Skip to content

Commit

Permalink
Add support for nested closures
Browse files Browse the repository at this point in the history
  • Loading branch information
gayanper committed May 18, 2023
1 parent 9ecf3b7 commit 94db159
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -298,15 +298,22 @@ public CompletableFuture<Either<List<CompletionItem>, CompletionList>> completio
}
}
Set<MethodCallExpression> methodCalls = this.completionVisitor.getMethodCalls(uri);
MethodCallExpression containingCall = null;
List<MethodCallExpression> containingCallPath = new ArrayList<>();
MethodCallExpression lastCall = null;
for (MethodCallExpression call : methodCalls) {
Expression expression = call.getArguments();
Range range = LSPUtils.toRange(expression);
if (Ranges.containsPosition(range, params.getPosition())
&& (containingCall == null || Ranges.containsRange(LSPUtils.toRange(containingCall.getArguments()),
LSPUtils.toRange(call.getArguments())))) {
// find inner containing call
containingCall = call;
if (Ranges.containsPosition(range, params.getPosition())) {
lastCall = call;
if (Ranges.containsRange(LSPUtils.toRange(lastCall.getArguments()),
LSPUtils.toRange(call.getArguments()))) {
// if current call contains inside last call, nested closure.
containingCallPath.add(call);
} else if (Ranges.containsRange(LSPUtils.toRange(call.getArguments()),
LSPUtils.toRange(lastCall.getArguments()))) {
// if current call is the parent of last call, nested closure.
containingCallPath.add(containingCallPath.size() - 1, call);
}
}
}
this.libraryResolver.loadGradleClasses();
Expand All @@ -315,11 +322,11 @@ public CompletableFuture<Either<List<CompletionItem>, CompletionList>> completio
CompletionHandler handler = new CompletionHandler();
// check again
String projectPath = Utils.getFolderPath(uri);
if (containingCall == null && isGradleRoot(uri, params.getPosition())) {
if (containingCallPath.isEmpty() && isGradleRoot(uri, params.getPosition())) {
return CompletableFuture.completedFuture(Either.forLeft(handler.getCompletionItems(null,
Paths.get(uri).getFileName().toString(), this.libraryResolver, javaPluginsIncluded, projectPath)));
}
return CompletableFuture.completedFuture(Either.forLeft(handler.getCompletionItems(containingCall,
return CompletableFuture.completedFuture(Either.forLeft(handler.getCompletionItems(containingCallPath,
Paths.get(uri).getFileName().toString(), this.libraryResolver, javaPluginsIncluded, projectPath)));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.bcel.classfile.Attribute;
import org.apache.bcel.classfile.FieldOrMethod;
import org.apache.bcel.classfile.JavaClass;
Expand All @@ -35,20 +36,21 @@ public class CompletionHandler {
private static String SETTING_GRADLE = "settings.gradle";
private static String DEPENDENCYHANDLER_CLASS = "org.gradle.api.artifacts.dsl.DependencyHandler";

public List<CompletionItem> getCompletionItems(MethodCallExpression containingCall, String fileName,
public List<CompletionItem> getCompletionItems(List<MethodCallExpression> containingCallPath, String fileName,
GradleLibraryResolver resolver, boolean javaPluginsIncluded, String projectPath) {
List<CompletionItem> results = new ArrayList<>();
Set<String> resultSet = new HashSet<>();
List<String> delegateClassNames = new ArrayList<>();
if (containingCall == null) {
if (containingCallPath.isEmpty()) {
if (fileName.equals(BUILD_GRADLE)) {
delegateClassNames.add(GradleDelegate.getDefault());
} else if (fileName.equals(SETTING_GRADLE)) {
delegateClassNames.add(GradleDelegate.getSettings());
}
results.addAll(getCompletionItemsFromExtClosures(resolver, projectPath, resultSet));
} else {
String methodName = containingCall.getMethodAsString();
String methodName = containingCallPath.stream().map(MethodCallExpression::getMethodAsString)
.collect(Collectors.joining("."));
List<CompletionItem> re = getCompletionItemsFromExtClosures(resolver, projectPath, methodName, resultSet);
results.addAll(re);
List<String> delegates = GradleDelegate.getDelegateMap().get(methodName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
Expand Down Expand Up @@ -192,31 +194,60 @@ private List<GradleClosure> getPluginClosures(Project project) {
for (ExtensionSchema schema : extensionsSchema.getElements()) {
TypeOf<?> publicType = schema.getPublicType();
Class<?> concreteClass = publicType.getConcreteClass();
List<GradleMethod> methods = new ArrayList<>();
List<GradleField> fields = new ArrayList<>();
for (Method method : concreteClass.getMethods()) {
String name = method.getName();
List<String> parameterTypes = new ArrayList<>();
for (Class<?> parameterType : method.getParameterTypes()) {
parameterTypes.add(parameterType.getName());
}
methods.add(new DefaultGradleMethod(name, parameterTypes, isDeprecated(method)));
int modifiers = method.getModifiers();
// See:
// https://docs.gradle.org/current/userguide/custom_gradle_types.html#managed_properties
// we offer managed properties for an abstract getter method
if (name.startsWith("get") && name.length() > 3 && Modifier.isPublic(modifiers)
&& Modifier.isAbstract(modifiers)) {
fields.add(new DefaultGradleField(name.substring(3, 4).toLowerCase() + name.substring(4),
isDeprecated(method)));
closures.addAll(buildClosure(schema.getName(), concreteClass));
}
return closures;
}

/**
* @param closureName
* @param concreteClass
* @return
*/
private List<DefaultGradleClosure> buildClosure(String closureName, Class<?> concreteClass) {
List<DefaultGradleClosure> closures = new ArrayList<>();
List<GradleMethod> methods = new ArrayList<>();
List<GradleField> fields = new ArrayList<>();
for (Method method : concreteClass.getMethods()) {
String name = method.getName();
List<String> parameterTypes = new ArrayList<>();
for (Class<?> parameterType : method.getParameterTypes()) {
parameterTypes.add(parameterType.getName());
}

// check for nested closure methods and include them in the final closure list
// for completions.
if (method.getGenericParameterTypes().length == 1) {
Type parameterType = method.getGenericParameterTypes()[0];
if (parameterType.getTypeName().startsWith("org.gradle.api.Action<")
&& (parameterType instanceof ParameterizedType)) {
Type[] actualTypeArguments = ((ParameterizedType) parameterType).getActualTypeArguments();
if (actualTypeArguments.length == 1) {
try {
closures.addAll(buildClosure(closureName.concat(".").concat(name),
concreteClass.getClassLoader().loadClass(actualTypeArguments[0].getTypeName())));
} catch (ClassNotFoundException e) {
// continue if we cannot find the extension class.
}
}
}
}
for (Field field : concreteClass.getFields()) {
fields.add(new DefaultGradleField(field.getName(), isDeprecated(field)));

methods.add(new DefaultGradleMethod(name, parameterTypes, isDeprecated(method)));
int modifiers = method.getModifiers();
// See:
// https://docs.gradle.org/current/userguide/custom_gradle_types.html#managed_properties
// we offer managed properties for an abstract getter method
if (name.startsWith("get") && name.length() > 3 && Modifier.isPublic(modifiers)
&& Modifier.isAbstract(modifiers)) {
fields.add(new DefaultGradleField(name.substring(3, 4).toLowerCase() + name.substring(4),
isDeprecated(method)));
}
DefaultGradleClosure closure = new DefaultGradleClosure(schema.getName(), methods, fields);
closures.add(closure);
}
for (Field field : concreteClass.getFields()) {
fields.add(new DefaultGradleField(field.getName(), isDeprecated(field)));
}
closures.add(new DefaultGradleClosure(closureName, methods, fields));
return closures;
}

Expand Down

0 comments on commit 94db159

Please sign in to comment.