Skip to content

Commit 5de9994

Browse files
committed
Add support for nested closures
1 parent 9ecf3b7 commit 5de9994

3 files changed

Lines changed: 73 additions & 33 deletions

File tree

gradle-language-server/src/main/java/com/microsoft/gradle/GradleServices.java

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -298,15 +298,22 @@ public CompletableFuture<Either<List<CompletionItem>, CompletionList>> completio
298298
}
299299
}
300300
Set<MethodCallExpression> methodCalls = this.completionVisitor.getMethodCalls(uri);
301-
MethodCallExpression containingCall = null;
301+
List<MethodCallExpression> containingCallPath = new ArrayList<>();
302+
MethodCallExpression lastCall = null;
302303
for (MethodCallExpression call : methodCalls) {
303304
Expression expression = call.getArguments();
304305
Range range = LSPUtils.toRange(expression);
305-
if (Ranges.containsPosition(range, params.getPosition())
306-
&& (containingCall == null || Ranges.containsRange(LSPUtils.toRange(containingCall.getArguments()),
307-
LSPUtils.toRange(call.getArguments())))) {
308-
// find inner containing call
309-
containingCall = call;
306+
if (Ranges.containsPosition(range, params.getPosition())) {
307+
lastCall = call;
308+
if (Ranges.containsRange(LSPUtils.toRange(lastCall.getArguments()),
309+
LSPUtils.toRange(call.getArguments()))) {
310+
// if current call contains inside last call, nested closure.
311+
containingCallPath.add(call);
312+
} else if (Ranges.containsRange(LSPUtils.toRange(call.getArguments()),
313+
LSPUtils.toRange(lastCall.getArguments()))) {
314+
// if current call is the parent of last call, nested closure.
315+
containingCallPath.add(containingCallPath.size() - 1, call);
316+
}
310317
}
311318
}
312319
this.libraryResolver.loadGradleClasses();
@@ -315,11 +322,11 @@ public CompletableFuture<Either<List<CompletionItem>, CompletionList>> completio
315322
CompletionHandler handler = new CompletionHandler();
316323
// check again
317324
String projectPath = Utils.getFolderPath(uri);
318-
if (containingCall == null && isGradleRoot(uri, params.getPosition())) {
319-
return CompletableFuture.completedFuture(Either.forLeft(handler.getCompletionItems(null,
325+
if (containingCallPath.isEmpty() && isGradleRoot(uri, params.getPosition())) {
326+
return CompletableFuture.completedFuture(Either.forLeft(handler.getCompletionItems(Collections.emptyList(),
320327
Paths.get(uri).getFileName().toString(), this.libraryResolver, javaPluginsIncluded, projectPath)));
321328
}
322-
return CompletableFuture.completedFuture(Either.forLeft(handler.getCompletionItems(containingCall,
329+
return CompletableFuture.completedFuture(Either.forLeft(handler.getCompletionItems(containingCallPath,
323330
Paths.get(uri).getFileName().toString(), this.libraryResolver, javaPluginsIncluded, projectPath)));
324331
}
325332

gradle-language-server/src/main/java/com/microsoft/gradle/handlers/CompletionHandler.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import java.util.HashSet;
1818
import java.util.List;
1919
import java.util.Set;
20+
import java.util.stream.Collectors;
2021
import org.apache.bcel.classfile.Attribute;
2122
import org.apache.bcel.classfile.FieldOrMethod;
2223
import org.apache.bcel.classfile.JavaClass;
@@ -35,20 +36,21 @@ public class CompletionHandler {
3536
private static String SETTING_GRADLE = "settings.gradle";
3637
private static String DEPENDENCYHANDLER_CLASS = "org.gradle.api.artifacts.dsl.DependencyHandler";
3738

38-
public List<CompletionItem> getCompletionItems(MethodCallExpression containingCall, String fileName,
39+
public List<CompletionItem> getCompletionItems(List<MethodCallExpression> containingCallPath, String fileName,
3940
GradleLibraryResolver resolver, boolean javaPluginsIncluded, String projectPath) {
4041
List<CompletionItem> results = new ArrayList<>();
4142
Set<String> resultSet = new HashSet<>();
4243
List<String> delegateClassNames = new ArrayList<>();
43-
if (containingCall == null) {
44+
if (containingCallPath.isEmpty()) {
4445
if (fileName.equals(BUILD_GRADLE)) {
4546
delegateClassNames.add(GradleDelegate.getDefault());
4647
} else if (fileName.equals(SETTING_GRADLE)) {
4748
delegateClassNames.add(GradleDelegate.getSettings());
4849
}
4950
results.addAll(getCompletionItemsFromExtClosures(resolver, projectPath, resultSet));
5051
} else {
51-
String methodName = containingCall.getMethodAsString();
52+
String methodName = containingCallPath.stream().map(MethodCallExpression::getMethodAsString)
53+
.collect(Collectors.joining("."));
5254
List<CompletionItem> re = getCompletionItemsFromExtClosures(resolver, projectPath, methodName, resultSet);
5355
results.addAll(re);
5456
List<String> delegates = GradleDelegate.getDelegateMap().get(methodName);

gradle-plugin/src/main/java/com/microsoft/gradle/GradleProjectModelBuilder.java

Lines changed: 52 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
import java.lang.reflect.Field;
1616
import java.lang.reflect.Method;
1717
import java.lang.reflect.Modifier;
18+
import java.lang.reflect.ParameterizedType;
19+
import java.lang.reflect.Type;
1820
import java.util.ArrayList;
1921
import java.util.Collections;
2022
import java.util.HashSet;
@@ -192,31 +194,60 @@ private List<GradleClosure> getPluginClosures(Project project) {
192194
for (ExtensionSchema schema : extensionsSchema.getElements()) {
193195
TypeOf<?> publicType = schema.getPublicType();
194196
Class<?> concreteClass = publicType.getConcreteClass();
195-
List<GradleMethod> methods = new ArrayList<>();
196-
List<GradleField> fields = new ArrayList<>();
197-
for (Method method : concreteClass.getMethods()) {
198-
String name = method.getName();
199-
List<String> parameterTypes = new ArrayList<>();
200-
for (Class<?> parameterType : method.getParameterTypes()) {
201-
parameterTypes.add(parameterType.getName());
202-
}
203-
methods.add(new DefaultGradleMethod(name, parameterTypes, isDeprecated(method)));
204-
int modifiers = method.getModifiers();
205-
// See:
206-
// https://docs.gradle.org/current/userguide/custom_gradle_types.html#managed_properties
207-
// we offer managed properties for an abstract getter method
208-
if (name.startsWith("get") && name.length() > 3 && Modifier.isPublic(modifiers)
209-
&& Modifier.isAbstract(modifiers)) {
210-
fields.add(new DefaultGradleField(name.substring(3, 4).toLowerCase() + name.substring(4),
211-
isDeprecated(method)));
197+
closures.addAll(buildClosure(schema.getName(), concreteClass));
198+
}
199+
return closures;
200+
}
201+
202+
/**
203+
* @param closureName
204+
* @param concreteClass
205+
* @return
206+
*/
207+
private List<DefaultGradleClosure> buildClosure(String closureName, Class<?> concreteClass) {
208+
List<DefaultGradleClosure> closures = new ArrayList<>();
209+
List<GradleMethod> methods = new ArrayList<>();
210+
List<GradleField> fields = new ArrayList<>();
211+
for (Method method : concreteClass.getMethods()) {
212+
String name = method.getName();
213+
List<String> parameterTypes = new ArrayList<>();
214+
for (Class<?> parameterType : method.getParameterTypes()) {
215+
parameterTypes.add(parameterType.getName());
216+
}
217+
218+
// check for nested closure methods and include them in the final closure list
219+
// for completions.
220+
if (method.getGenericParameterTypes().length == 1) {
221+
Type parameterType = method.getGenericParameterTypes()[0];
222+
if (parameterType.getTypeName().startsWith("org.gradle.api.Action<")
223+
&& (parameterType instanceof ParameterizedType)) {
224+
Type[] actualTypeArguments = ((ParameterizedType) parameterType).getActualTypeArguments();
225+
if (actualTypeArguments.length == 1) {
226+
try {
227+
closures.addAll(buildClosure(closureName.concat(".").concat(name),
228+
concreteClass.getClassLoader().loadClass(actualTypeArguments[0].getTypeName())));
229+
} catch (ClassNotFoundException e) {
230+
// continue if we cannot find the extension class.
231+
}
232+
}
212233
}
213234
}
214-
for (Field field : concreteClass.getFields()) {
215-
fields.add(new DefaultGradleField(field.getName(), isDeprecated(field)));
235+
236+
methods.add(new DefaultGradleMethod(name, parameterTypes, isDeprecated(method)));
237+
int modifiers = method.getModifiers();
238+
// See:
239+
// https://docs.gradle.org/current/userguide/custom_gradle_types.html#managed_properties
240+
// we offer managed properties for an abstract getter method
241+
if (name.startsWith("get") && name.length() > 3 && Modifier.isPublic(modifiers)
242+
&& Modifier.isAbstract(modifiers)) {
243+
fields.add(new DefaultGradleField(name.substring(3, 4).toLowerCase() + name.substring(4),
244+
isDeprecated(method)));
216245
}
217-
DefaultGradleClosure closure = new DefaultGradleClosure(schema.getName(), methods, fields);
218-
closures.add(closure);
219246
}
247+
for (Field field : concreteClass.getFields()) {
248+
fields.add(new DefaultGradleField(field.getName(), isDeprecated(field)));
249+
}
250+
closures.add(new DefaultGradleClosure(closureName, methods, fields));
220251
return closures;
221252
}
222253

0 commit comments

Comments
 (0)