Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support basic java plugin aware #989

Merged
merged 1 commit into from
Sep 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -267,14 +267,15 @@ public CompletableFuture<Either<List<CompletionItem>, CompletionList>> completio
containingCall = call;
}
}
boolean javaPluginsIncluded = this.libraryResolver.isJavaPluginsIncluded(this.completionVisitor.getPlugins(uri));
CompletionHandler handler = new CompletionHandler();
// check again
if (containingCall == null && isGradleRoot(uri, params.getPosition())) {
return CompletableFuture.completedFuture(Either
.forLeft(handler.getCompletionItems(null, Paths.get(uri).getFileName().toString(), this.libraryResolver)));
.forLeft(handler.getCompletionItems(null, Paths.get(uri).getFileName().toString(), this.libraryResolver, javaPluginsIncluded)));
}
return CompletableFuture.completedFuture(Either.forLeft(
handler.getCompletionItems(containingCall, Paths.get(uri).getFileName().toString(), this.libraryResolver)));
handler.getCompletionItems(containingCall, Paths.get(uri).getFileName().toString(), this.libraryResolver, javaPluginsIncluded)));
}

private boolean isGradleRoot(URI uri, Position position) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.GStringExpression;
import org.codehaus.groovy.ast.expr.MapEntryExpression;
import org.codehaus.groovy.ast.expr.MethodCallExpression;
import org.codehaus.groovy.ast.expr.NamedArgumentListExpression;
import org.codehaus.groovy.ast.expr.TupleExpression;
import org.codehaus.groovy.ast.stmt.BlockStatement;
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
import org.codehaus.groovy.ast.stmt.Statement;
Expand Down Expand Up @@ -60,6 +63,7 @@ public Range getRange() {
private Map<URI, Set<MethodCallExpression>> methodCalls = new HashMap<>();
private Map<URI, List<Statement>> statements = new HashMap<>();
private Map<URI, List<Expression>> constants = new HashMap<>();
private Map<URI, Set<String>> plugins = new HashMap<>();

public List<DependencyItem> getDependencies(URI uri) {
return this.dependencies.get(uri);
Expand All @@ -77,6 +81,10 @@ public List<Expression> getConstants(URI uri) {
return this.constants.get(uri);
}

public Set<String> getPlugins(URI uri) {
return this.plugins.get(uri);
}

public void visitCompilationUnit(URI uri, GradleCompilationUnit compilationUnit) {
this.currentUri = uri;
compilationUnit.iterator().forEachRemaining(unit -> visitSourceUnit(unit));
Expand All @@ -89,6 +97,7 @@ public void visitSourceUnit(SourceUnit unit) {
this.methodCalls.put(this.currentUri, new HashSet<>());
this.statements.put(this.currentUri, new ArrayList<>());
this.constants.put(this.currentUri, new ArrayList<>());
this.plugins.put(this.currentUri, new HashSet<>());
visitModule(moduleNode);
}
}
Expand All @@ -106,6 +115,16 @@ public void visitMethodCallExpression(MethodCallExpression node) {
this.methodCalls.get(this.currentUri).add(node);
if (node.getMethodAsString().equals("dependencies")) {
this.dependencies.get(this.currentUri).addAll(getDependencies(node));
} else if (node.getMethodAsString().equals("plugins")) {
// match plugins { id: ${id} }
List<String> plugins = getPluginFromPlugins(node);
this.plugins.get(this.currentUri).addAll(plugins);
} else if (node.getMethodAsString().equals("apply")) {
// match apply plugins: '${id}'
String plugin = getPluginFromApply(node);
if (plugin != null) {
this.plugins.get(this.currentUri).add(plugin);
}
}
super.visitMethodCallExpression(node);
}
Expand Down Expand Up @@ -162,6 +181,62 @@ private List<DependencyItem> getDependencies(ExpressionStatement expressionState
return Collections.emptyList();
}

private String getPluginFromApply(MethodCallExpression node) {
Expression argument = node.getArguments();
if (argument instanceof TupleExpression) {
List<Expression> expressions = ((TupleExpression)argument).getExpressions();
for (Expression expression : expressions) {
if (expression instanceof NamedArgumentListExpression) {
List<MapEntryExpression> mapEntryExpressions = ((NamedArgumentListExpression)expression).getMapEntryExpressions();
for (MapEntryExpression mapEntryExp: mapEntryExpressions) {
Expression keyExpression = mapEntryExp.getKeyExpression();
if (keyExpression instanceof ConstantExpression && keyExpression.getText().equals("plugin")) {
return mapEntryExp.getValueExpression().getText();
}
}
}
}
}
return null;
}

private List<String> getPluginFromPlugins(MethodCallExpression node) {
Expression objectExpression = node.getObjectExpression();
if (objectExpression instanceof MethodCallExpression) {
return getPluginFromPlugins((MethodCallExpression)objectExpression);
}
List<String> results = new ArrayList<>();
Expression argument = node.getArguments();
if (argument instanceof ArgumentListExpression) {
List<Expression> expressions = ((ArgumentListExpression)argument).getExpressions();
for (Expression expression : expressions) {
if (expression instanceof ConstantExpression && node.getMethodAsString().equals("id")) {
results.add(expression.getText());
} else if (expression instanceof ClosureExpression) {
Statement code = ((ClosureExpression)expression).getCode();
if (code instanceof BlockStatement) {
results.addAll(getPluginFromPlugins((BlockStatement) code));
}
}
}
}
return results;
}

private List<String> getPluginFromPlugins(BlockStatement code) {
List<String> results = new ArrayList<>();
List<Statement> statements = code.getStatements();
for (Statement statement : statements) {
if (statement instanceof ExpressionStatement) {
Expression expression = ((ExpressionStatement)statement).getExpression();
if (expression instanceof MethodCallExpression) {
results.addAll(getPluginFromPlugins((MethodCallExpression) expression));
}
}
}
return results;
}

@Override
public void visitConstantExpression(ConstantExpression expression) {
this.constants.get(currentUri).add(expression);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ public class CompletionHandler {

private static String BUILD_GRADLE = "build.gradle";
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, GradleLibraryResolver resolver) {
public List<CompletionItem> getCompletionItems(MethodCallExpression containingCall, String fileName, GradleLibraryResolver resolver, boolean javaPluginsIncluded) {
String delegateClassName = null;
if (containingCall == null) {
if (fileName.equals(BUILD_GRADLE)) {
Expand All @@ -51,22 +52,22 @@ public List<CompletionItem> getCompletionItems(MethodCallExpression containingCa
if (delegateClass == null) {
return Collections.emptyList();
}
return getCompletionItemsFromClass(delegateClass, resolver, new HashSet<>());
return getCompletionItemsFromClass(delegateClass, resolver, javaPluginsIncluded, new HashSet<>());
}

private List<CompletionItem> getCompletionItemsFromClass(JavaClass javaClass, GradleLibraryResolver resolver, Set<String> resultSet) {
private List<CompletionItem> getCompletionItemsFromClass(JavaClass javaClass, GradleLibraryResolver resolver, boolean javaPluginsIncluded, Set<String> resultSet) {
if (javaClass == null) {
return Collections.emptyList();
}
List<CompletionItem> results = new ArrayList<>();
for (String superInterface : javaClass.getInterfaceNames()) {
if (resolver.getGradleLibraries().containsKey(superInterface)) {
results.addAll(getCompletionItemsFromClass(resolver.getGradleLibraries().get(superInterface), resolver, resultSet));
results.addAll(getCompletionItemsFromClass(resolver.getGradleLibraries().get(superInterface), resolver, javaPluginsIncluded, resultSet));
}
}
String superClass = javaClass.getSuperclassName();
if (resolver.getGradleLibraries().containsKey(superClass)) {
results.addAll(getCompletionItemsFromClass(resolver.getGradleLibraries().get(superClass), resolver, resultSet));
results.addAll(getCompletionItemsFromClass(resolver.getGradleLibraries().get(superClass), resolver, javaPluginsIncluded, resultSet));
}
List<String> methodNames = new ArrayList<>();
Method[] methods = javaClass.getMethods();
Expand Down Expand Up @@ -128,6 +129,22 @@ private List<CompletionItem> getCompletionItemsFromClass(JavaClass javaClass, Gr
}
}
}
if (javaPluginsIncluded && javaClass.getClassName().equals(DEPENDENCYHANDLER_CLASS)) {
// for dependency {}, we offer java configurations if there is any applied java plugin
for (String plugin : resolver.getJavaConfigurations()) {
StringBuilder builder = new StringBuilder();
builder.append(plugin);
builder.append("(Object... o)");
StringBuilder insertBuilder = new StringBuilder();
insertBuilder.append(plugin);
insertBuilder.append("($0)");
CompletionItem item = new CompletionItem(builder.toString());
item.setKind(CompletionItemKind.Function);
item.setInsertTextFormat(InsertTextFormat.Snippet);
item.setInsertText(insertBuilder.toString());
results.add(item);
}
}
return results;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,39 @@
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.bcel.classfile.ClassFormatException;
import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.Field;
import org.apache.bcel.classfile.JavaClass;

public class GradleLibraryResolver {

private static String JAVA_PLUGIN = "org.gradle.api.plugins.JavaPlugin";

private Map<String, JavaClass> gradleLibraries = new HashMap<>();
private Set<String> javaConfigurations = new HashSet<>();
private Set<String> javaPlugins = new HashSet<>();
private String gradleHome;
private String gradleVersion;
private boolean gradleWrapperEnabled;
private String gradleUserHome;
private Path workspacePath;

public GradleLibraryResolver() {
this.javaPlugins.addAll(Arrays.asList("java", "application", "groovy", "java-library", "war"));
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rest four plugins apply java plugin as well.

}

public void setGradleHome(String gradleHome) {
this.gradleHome = gradleHome;
}
Expand All @@ -60,6 +73,10 @@ public Map<String, JavaClass> getGradleLibraries() {
return this.gradleLibraries;
}

public Set<String> getJavaConfigurations() {
return this.javaConfigurations;
}

public void resolve() {
Path gradleUserHomePath = (this.gradleUserHome == null) ? Path.of(System.getProperty("user.home"), ".gradle")
: Path.of(this.gradleUserHome);
Expand All @@ -86,6 +103,7 @@ public void resolve() {
}
JarFile pluginLibJar = new JarFile(pluginLibFile);
getGradleLibraries(pluginLibFile.toPath(), pluginLibJar);
resolveJavaConfigurations();
} catch (Exception e) {
// Do Nothing
}
Expand Down Expand Up @@ -190,4 +208,33 @@ private void getGradleLibraries(Path jarPath, JarFile jarFile) {
}
}
}

private void resolveJavaConfigurations() {
JavaClass javaPluginClass = this.gradleLibraries.get(GradleLibraryResolver.JAVA_PLUGIN);
if (javaPluginClass == null) {
return;
}
for (Field field : javaPluginClass.getFields()) {
if (field.getName().endsWith("CONFIGURATION_NAME")) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.javaConfigurations.add(removeQuotes(field.getConstantValue().toString()));
}
}
}

private static String removeQuotes(String original) {
// for those fields parsed from class files, we get ""values"", so we remove the starting and ending quotes here
if (original.length() < 3) {
return original;
}
return original.substring(1, original.length() - 1);
}

public boolean isJavaPluginsIncluded(Set<String> plugins) {
for (String plugin : plugins) {
if (this.javaPlugins.contains(plugin)) {
return true;
}
}
return false;
}
}