Skip to content

Instantly share code, notes, and snippets.

@romain-grecourt
Created November 8, 2021 22:07
Show Gist options
  • Save romain-grecourt/71c04cfc7378dab98a7faf37bb5fc86f to your computer and use it in GitHub Desktop.
Save romain-grecourt/71c04cfc7378dab98a7faf37bb5fc86f to your computer and use it in GitHub Desktop.
idea.gdsl
// see https://confluence.jetbrains.com/display/GRVY/Scripting+IDE+for+DSL+awareness
import com.intellij.lang.jvm.types.JvmReferenceType
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiClassObjectAccessExpression
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiParameter
import com.intellij.psi.PsiQualifiedNamedElement
def ctx = context(pathRegexp: /.*\.groovy/)
def dslFactoryClassName = 'javaposse.jobdsl.dsl.DslFactory'
contributor(ctx, {
method(name: 'properties', type: Object, params: [body: Closure], doc: 'properties')
delegatesTo(findClass(dslFactoryClassName))
})
//PrintStream out = new PrintStream(new FileOutputStream("/tmp/out.log"), true)
class DelegateToVisitor extends PsiElementVisitor {
String delegate = null
@Override
void visitElement(PsiElement element) {
if ((element instanceof PsiParameter)) {
PsiParameter param = (PsiParameter) element
for (annotation in param.getAnnotations()) {
if (annotation.getQualifiedName() == 'groovy.lang.DelegatesTo') {
for (attr in annotation.getParameterList().getAttributes()) {
if (attr.getAttributeName() == 'value') {
def value = attr.getValue()
if (!value instanceof PsiClassObjectAccessExpression) {
continue
}
def operand = ((PsiClassObjectAccessExpression) value).getOperand()
delegate = operand.getType().getCanonicalText()
return
}
}
}
}
}
}
}
def static findDelegateTo(PsiMethod method) {
for (p in method.getParameters()) {
def pType = p.getType()
if (pType instanceof JvmReferenceType) {
if (((JvmReferenceType) pType).getName() == "Closure") {
DelegateToVisitor visitor = new DelegateToVisitor()
p.getSourceElement().accept(visitor)
if (visitor.delegate != null) {
return visitor.delegate
}
}
}
}
return null
}
def static findDelegate(method) {
def delegate = findDelegateTo(method)
if (delegate == null) {
def annotation = method.getAnnotation('javaposse.jobdsl.dsl.RequiresPlugin')
if (annotation != null) {
def mrt = method.getReturnType().getCanonicalText()
if (mrt != 'void') {
delegate = mrt
}
}
}
return delegate
}
def static signature(PsiMethod method) {
def qName = ((PsiQualifiedNamedElement) method.getParent()).getQualifiedName()
// TODO include parameters
return qName + "#" + method.getName()
}
def static findEnclosingCall(Closure func, Map<String, Tuple2<PsiMethod, PsiClass>> delegates, String signature) {
for (entry in delegates.entrySet()) {
//noinspection GrDeprecatedAPIUsage
if (!(signature != null && signature == entry.getKey())
&& func(entry.getValue().getFirst().getName()) != null) {
return entry
}
}
return null
}
def closures = context(scope: closureScope( null))
contributor(closures) {
// navigate the code starting from DslFactory
// and discover the methods that can be delegated
Map<String, Tuple2<PsiMethod, PsiClass>> delegates = [:]
def classes = [ findClass(dslFactoryClassName) ]
while (!classes.isEmpty()) {
def clazz = classes.pop()
for (m in clazz.getMethods()) {
if (delegates.containsKey(signature(m))) {
continue
}
def delegate = findDelegate(m)
if (delegate != null) {
def delegateClass = findClass(delegate)
if (delegateClass != null) {
classes.push(delegateClass)
delegates.put(signature(m), new Tuple2<>(m, delegateClass))
}
}
}
}
// short-hand to invoke findEnclosingCall
def _findEnclosingCall = { String current ->
return findEnclosingCall({ s ->
return enclosingCall((String) s)
}, delegates, current)
}
// delegate enclosing calls found up to 50 times
def entry = _findEnclosingCall(null)
for (int depth=0 ; depth < 50 && entry != null; depth++) {
//noinspection GrDeprecatedAPIUsage
delegatesTo(entry.getValue().getSecond())
entry = _findEnclosingCall(entry.getKey())
}
// Hard-code some of the dynamic mappings
// E.g. periodic trigger
if (enclosingCall("triggers")) {
method(name: 'periodicFolderTrigger', type: Object, params: [body: Closure], doc: 'configure the scanning')
if (enclosingCall("periodicFolderTrigger")) {
method(name: 'interval', type: Object, params: [interval: String], doc: 'set the scanning interval, e.g. \'1m\'')
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment