From 183be78afe795311eef78cc48066aaf196dd9d54 Mon Sep 17 00:00:00 2001 From: Shi Chen Date: Tue, 7 Sep 2021 10:18:24 +0800 Subject: [PATCH] support basic plugin aware --- .../com/microsoft/gradle/GradleServices.java | 5 +- .../gradle/compile/CompletionVisitor.java | 75 +++++++++++++++++++ .../gradle/handlers/CompletionHandler.java | 27 +++++-- .../resolver/GradleLibraryResolver.java | 47 ++++++++++++ 4 files changed, 147 insertions(+), 7 deletions(-) diff --git a/gradle-language-server/src/main/java/com/microsoft/gradle/GradleServices.java b/gradle-language-server/src/main/java/com/microsoft/gradle/GradleServices.java index bdaaf59ac..68996bb04 100644 --- a/gradle-language-server/src/main/java/com/microsoft/gradle/GradleServices.java +++ b/gradle-language-server/src/main/java/com/microsoft/gradle/GradleServices.java @@ -267,14 +267,15 @@ public CompletableFuture, 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) { diff --git a/gradle-language-server/src/main/java/com/microsoft/gradle/compile/CompletionVisitor.java b/gradle-language-server/src/main/java/com/microsoft/gradle/compile/CompletionVisitor.java index 59245c6f0..2fce6c1ff 100644 --- a/gradle-language-server/src/main/java/com/microsoft/gradle/compile/CompletionVisitor.java +++ b/gradle-language-server/src/main/java/com/microsoft/gradle/compile/CompletionVisitor.java @@ -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; @@ -60,6 +63,7 @@ public Range getRange() { private Map> methodCalls = new HashMap<>(); private Map> statements = new HashMap<>(); private Map> constants = new HashMap<>(); + private Map> plugins = new HashMap<>(); public List getDependencies(URI uri) { return this.dependencies.get(uri); @@ -77,6 +81,10 @@ public List getConstants(URI uri) { return this.constants.get(uri); } + public Set getPlugins(URI uri) { + return this.plugins.get(uri); + } + public void visitCompilationUnit(URI uri, GradleCompilationUnit compilationUnit) { this.currentUri = uri; compilationUnit.iterator().forEachRemaining(unit -> visitSourceUnit(unit)); @@ -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); } } @@ -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 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); } @@ -162,6 +181,62 @@ private List getDependencies(ExpressionStatement expressionState return Collections.emptyList(); } + private String getPluginFromApply(MethodCallExpression node) { + Expression argument = node.getArguments(); + if (argument instanceof TupleExpression) { + List expressions = ((TupleExpression)argument).getExpressions(); + for (Expression expression : expressions) { + if (expression instanceof NamedArgumentListExpression) { + List 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 getPluginFromPlugins(MethodCallExpression node) { + Expression objectExpression = node.getObjectExpression(); + if (objectExpression instanceof MethodCallExpression) { + return getPluginFromPlugins((MethodCallExpression)objectExpression); + } + List results = new ArrayList<>(); + Expression argument = node.getArguments(); + if (argument instanceof ArgumentListExpression) { + List 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 getPluginFromPlugins(BlockStatement code) { + List results = new ArrayList<>(); + List 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); diff --git a/gradle-language-server/src/main/java/com/microsoft/gradle/handlers/CompletionHandler.java b/gradle-language-server/src/main/java/com/microsoft/gradle/handlers/CompletionHandler.java index 93cdc1207..464d7bc81 100644 --- a/gradle-language-server/src/main/java/com/microsoft/gradle/handlers/CompletionHandler.java +++ b/gradle-language-server/src/main/java/com/microsoft/gradle/handlers/CompletionHandler.java @@ -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 getCompletionItems(MethodCallExpression containingCall, String fileName, GradleLibraryResolver resolver) { + public List getCompletionItems(MethodCallExpression containingCall, String fileName, GradleLibraryResolver resolver, boolean javaPluginsIncluded) { String delegateClassName = null; if (containingCall == null) { if (fileName.equals(BUILD_GRADLE)) { @@ -51,22 +52,22 @@ public List getCompletionItems(MethodCallExpression containingCa if (delegateClass == null) { return Collections.emptyList(); } - return getCompletionItemsFromClass(delegateClass, resolver, new HashSet<>()); + return getCompletionItemsFromClass(delegateClass, resolver, javaPluginsIncluded, new HashSet<>()); } - private List getCompletionItemsFromClass(JavaClass javaClass, GradleLibraryResolver resolver, Set resultSet) { + private List getCompletionItemsFromClass(JavaClass javaClass, GradleLibraryResolver resolver, boolean javaPluginsIncluded, Set resultSet) { if (javaClass == null) { return Collections.emptyList(); } List 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 methodNames = new ArrayList<>(); Method[] methods = javaClass.getMethods(); @@ -128,6 +129,22 @@ private List 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; } } diff --git a/gradle-language-server/src/main/java/com/microsoft/gradle/resolver/GradleLibraryResolver.java b/gradle-language-server/src/main/java/com/microsoft/gradle/resolver/GradleLibraryResolver.java index 2d3fcb8a2..f7200c6b8 100644 --- a/gradle-language-server/src/main/java/com/microsoft/gradle/resolver/GradleLibraryResolver.java +++ b/gradle-language-server/src/main/java/com/microsoft/gradle/resolver/GradleLibraryResolver.java @@ -16,9 +16,12 @@ 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; @@ -26,16 +29,26 @@ 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 gradleLibraries = new HashMap<>(); + private Set javaConfigurations = new HashSet<>(); + private Set 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")); + } + public void setGradleHome(String gradleHome) { this.gradleHome = gradleHome; } @@ -60,6 +73,10 @@ public Map getGradleLibraries() { return this.gradleLibraries; } + public Set getJavaConfigurations() { + return this.javaConfigurations; + } + public void resolve() { Path gradleUserHomePath = (this.gradleUserHome == null) ? Path.of(System.getProperty("user.home"), ".gradle") : Path.of(this.gradleUserHome); @@ -86,6 +103,7 @@ public void resolve() { } JarFile pluginLibJar = new JarFile(pluginLibFile); getGradleLibraries(pluginLibFile.toPath(), pluginLibJar); + resolveJavaConfigurations(); } catch (Exception e) { // Do Nothing } @@ -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")) { + 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 plugins) { + for (String plugin : plugins) { + if (this.javaPlugins.contains(plugin)) { + return true; + } + } + return false; + } }