Child pages
  • Scripting IDE for DSL awareness

Versions Compared


  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 4.0


GroovyDSL is a framework with a domain-specific language designed to define the behaviour of end-user DSLs as script files which are executed by the IDE on the fly, bringing new reference resolution and code completion logic into the scope of a project. GroovyDSL relies on the Program Structure Interface (PSI), a set of internal classes and interfaces of IntelliJ IDEA, which allow all programming languages to be described in a uniform way. Due to Groovy's meta-programming capabilities, the developer of DSL descriptions in GroovyDSL need not be aware of how PSI works. All interoperation with PSI is covered by calls to methods and properties of GroovyDSL scripts.

Stable builds of IntelliJ IDEA with embedded JetGroovy plugin and GroovyDSL support are available at

Writing a simple GroovyDSL script


Code Block
def ctx = context(ctype: "java.lang.Integer",
                  scope: scriptScope(name: "DayCounter.groovy"))

contributor([ctx]) {
  property name: "daysFromNow", type: "Calendar"


Switching back to the DayCounter script we'll see that `daysFromNow' is highlighted now as a class property, and, moreover, it's available for completion. This works pretty like Dynamic Properties feature, but GroovyDSL scripting gives much larger freedom in defining context. For example, by script we just have written, `daysFromNow' property will be available only on top of DayCounter scripts, not in others, whereas being defined with dynamic properties it would be available in all Groovy files in project. Of course, there are various scopes to define contexts of different granularity.

More examples of GroovyDSL scripts


GroovyDSL internal language relies on the IntelliJ IDEA open API, namely the set of Program Structure Interfaces (PSI). All GroovyDSL functions and properties are nothing but wrappers around existing PSI interfaces. To get an adequate code assistance with code completion it's strongly recommended to attach openapi.jar from $IDEA_DIR/lib folder as a library to an actual project. GroovyDSL scripts will be working fine even without it being attached, but in this cases case some references in GroovyDSL scripts will be marked as possibly unresolved or untyped. In light of said above, all standard PSI methods are available in GroovyDSL as well as synthetic ones, described below.


Code Block
def ctx2 = context(ctype: "Position")
contributor ([ctx2]) {
  // Add generic methods
  classType?.fields?.each {
    method name: "findAll${}Positions",
           type: 'java.util.Collection'

It's important to remark, that classType is a predefined property of a closure, passes passed as a parameter to the contributor() method and it's available only inside of it, whereas fields is an invocation of the getFields() method of a PsiClass class. Here we are also using Groovy's higher-order functions to iterate by method `each' through the collection of fetched fields.


Code Block
def ctx = context(scope: closureScope())

contributor([ctx], {
  def call = enclosingCall("run")
  if (call) {
    def method = call.bind()
    def clazz = method?.containingClass
    if ("Runner".equals(clazz?.qualName)) {

Several predefined GroovyDSL methods are used in this code fragment. enclosingCall(methodName) call returns a mathod method invocation expression of a given context with a name, matching methodName or null otherwise. delegatesTo method takes an expression and adds all member of its type to the augmented class reference. In the given example this is enclosing closure, since the context's scope is defined as closureScope().


Code Block
def ctx = context ctype: <ctype>,
                  filetypes: [<file_ext>*],
                  scope: <scope>,

Context definition in GroovyDSL is nothing but an invocation of a predefined method context with four optional arguments, each of which may be omitted. Without any arguments given invocation of context method returns the definition of an empty context, which has a meaning ``everywhere''. Below we describe possible values of optional parameters (in angle brackets).

Reference class type <ctype> A string, representing a fully qualified name of a class type to be augmented with the new behavior. This change affects both qualified and unqualified calls to methods and properties of a given class. In the case of an unqualified invocation, <ctype> is thought as a type of this reference. If this argument is omitted, java.lang.Object class type is taken as a type to be augmented.
Supported file types <file_ext><filetypes>* A possible empty list of comma-separated file extensions passed as strings or GString instances with an opening dot or without it. Since some dynamic behavior may be scoped only in specific groovy-like files with a specific file extension, for instance, Gant Gradle scripts, providing an explicit extension list gives a way to describe such cases. If this argument is omitted, file extension is taken to be ``.groovy'' by default.

Script type <scriptType> A string denoting the script type ID, e.g. 'gant', 'gradle', 'default'. Differs from <fileTypes> in that it doesn't depend on the extension. For example, the Gant scripts in Grails usually have groovy extension. Since IDEA X
Path pattern <pathRegexp> A regexp that should match the path to the Groovy file where your script contributes to. The path always uses forward / slashes. Since IDEA X
Context scope <scope> A scope of a current context. If no scope passed to a context, it is assumed to capture all possible places, where other conditions are applicable. It may be of three different kinds:


Contributors are entities which consume contexts as initialization parameters and answer to the question ``What?''. Contributors provide new properties and methods according to given contexts. We provide a set of utility methods to augment existing types with new behavior, which may be used in a contributor application as regular Groovy methods. New contributor may be defined by an invocation of a method contributor(), taking two parameters:
1. A list of contexts to be used to contribute a new behavior
2. OneZero-argument closure, taking a contexts and adding new behavior to the reference expression ``in the focus'' of a context


Extending GroovyDSL.

The core of GroovyDSL functionality is concentrated in a body of closures, passed to the contributor() method as a second parameter. All invocations of functions and properties inside of it are calls to wrappers around internal IntelliJ IDEA's PSI. There are two kinds of such wrappers. The first kind is unqualified ones, such as findClass(). The second kind is methods, added to the original PSI classes via the mix-in categories mechanism. For example, methods property is added externally by a GroovyDSL environment to the PsiClass class.


Code Block
def gdslScriptContext = context(scope: scriptScope(), filetypes: ["gdsl"])

contributor([gdslScriptContext]) {
  method name: "context", params: [args: [:]], type: "java.lang.Object"
  method name: "contributor", params: [contexts: "java.util.List", body: {}], type: void

  // scopes
  property name: "closureScope", type: {}
  property name: "scriptScope", type: {}

def contributorBody = context(scope: closureScope(isArg: true))

contributor([contributorBody]) {
  if (enclosingCall("contributor")) {
    method name: "method", type: "void", params: [args: [
            parameter(name:'name',, doc:'Method name'),
            parameter(name:'params',, doc:'A map representing method parameters'),
            parameter(name:'namedParams',, doc:'''A list representing method named parameters.<br>
Its elements should be calls to <code>parameter</code> method.'''),
            parameter(name:'type',, doc:'Return type name of the method'),
            parameter(name:'doc',, doc:'Method documentation text'),
    ]], doc:'Describe a DSL method'
    method name: "property", type: "void", params: [args: [
            parameter(name:'name',, doc:'Property name'),
            parameter(name:'type',, doc:'Property type name'),
            parameter(name:'doc',, doc:'Property documentation text'),
    ]], doc:'Describe a DSL property'
    method name: "parameter", type: "Parameter", params: [args: [
            parameter(name:'name',, doc:'Parameter name'),
            parameter(name:'type',, doc:'Parameter type name'),
            parameter(name:'doc',, doc:'Parameter documentation text'),
    ]], doc:'Describe a method named parameter'

    method name: "add", type: "void", params: [member: "com.intellij.psi.PsiMember"]
    method name: "findClass", type: "com.intellij.psi.PsiClass", params: [name: "java.lang.String"]
    method name: "delegatesTo", type: "void", params: [clazzelem: "com.intellij.psi.PsiClassPsiElement"]

    method name: "delegatesToenclosingCall",
           type: "voidcom.intellij.psi.PsiElement",
           params: [exprmethodName: "org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpressionjava.lang.String"]

    method name: "enclosingCallenclosingMethod", type: "com.intellij.psi.PsiMethod"
    method name: "enclosingMember",   type: "com.intellij.psi.PsiElementPsiMember",
    method name:      params: [methodName: "java.lang.String"]"enclosingClass", type: "com.intellij.psi.PsiClass"

    property name: "place", type: "com.intellij.psi.PsiElement"
  // Reference toproperty the place in the focus of a context
    property name: "classType", typename: "classType", type: "com.intellij.psi.PsiClass"

def psiClassContext = context(scope: closureScope(isArg: true), ctype: "com.intellij.psi.PsiClass" // Class type to be augmented
contributor([psiClassContext]) {
  method name: "getMethods", type: "java.util.Collection"
  method name: "getQualName", type: "java.lang.String"

def psiClassContextpsiMemberContext = context(scope: closureScope(isArg: true), ctype: "com.intellij.psi.PsiClassPsiMember")
contributor([psiClassContextpsiMemberContext]) {
  method name: "getMethodshasAnnotation", typeparams: [name: "java.utillang.CollectionString"], type: "boolean"
  method name: "getQualNamehasAnnotation", type: "boolean"
  method name: "getAnnotation", params: [name: "java.lang.String"], type: "com.intellij.psi.PsiAnnotation"
  method name: "hasAnnotationgetAnnotations", params: [name: "java.lang.String"], type: "booleanjava.util.Collection<com.intellij.psi.PsiAnnotation>"

def psiFieldContext = context(scope: closureScope(isArg: true), ctype: "com.intellij.psi.PsiField")
contributor([psiFieldContext]) {
  method name: "getAnnotationgetClassType", type: "com.intellij.psi.PsiAnnotationPsiClass"

def psiMethodContext = context(scope: closureScope(isArg: true), ctype: "com.intellij.psi.PsiMethod")
contributor([psiMethodContext]) {
  method name: "getParamStringVector", type: "java.util.Map"

def psiElementContext = context(scope: closureScope(isArg: true), ctype: "com.intellij.psi.PsiElement")
contributor([psiElementContext]) {
  method name: "bind", type: "com.intellij.psi.PsiElement"
  method name: "eval", type: "java.lang.Object"
  method name: "asList", type: "java.util.collection<com.intellij.psi.PsiElement>"
  method name: "getQualifier", type: "com.intellij.psi.PsiElement"

def expressionContext = context(scope: closureScope(isArg: true),
                                ctype: "org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression")
contributor([expressionContext]) {
  method name: "getArguments", type: "java.util.Collection"
  method name: "getClassType", type: "com.intellij.psi.PsiClass"

Describing custom annotations

GroovyDSL was used to describe compile-time AST transformations, introduced in Groovy 1.6. All scrip files listed below are bundled into last distributions of IntelliJ IDEA 9.

Delegate transformation:  delegateTransform.gdsl
Category and Mixin transformations: categoryTransform.gdsl
Newify transformation:  newifyTransform.gdsl
Singleton transformation:  singletonTransform.gdsl
Bindable and Vetoable transformation:  bindableTransform.gdsl, vetoableTransform.gdsl


In the new versions of IDEA, GDSL may add some new primitives to describe your DSLs. If you want your single script to work with various IDEA versions (>=9.0.3), you may use supportVersion:

Code Block

if (supportsVersion("10.0")) {
  contributor(ctxIntroducedIn10:methodIntroducedIn10()) { ... }