Created
November 8, 2021 22:07
-
-
Save romain-grecourt/71c04cfc7378dab98a7faf37bb5fc86f to your computer and use it in GitHub Desktop.
idea.gdsl
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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