Snippets and Spring Data completions

This commit is contained in:
BoykoAlex
2022-02-08 16:19:35 -05:00
parent 162de16a77
commit aed3398ecb
7 changed files with 178 additions and 53 deletions

View File

@@ -15,13 +15,15 @@ import java.util.List;
import java.util.Optional;
import org.eclipse.lsp4j.CompletionItemKind;
import org.openrewrite.internal.lang.Nullable;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.J.Annotation;
import org.openrewrite.java.tree.J.ClassDeclaration;
import org.openrewrite.java.tree.JavaType;
import org.openrewrite.java.tree.JavaType.FullyQualified;
import org.openrewrite.java.tree.JavaType.Parameterized;
import org.openrewrite.java.tree.TypeUtils;
import org.springframework.ide.vscode.boot.java.handlers.CompletionProvider;
import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
import org.springframework.ide.vscode.boot.java.utils.ORAstUtils;
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
@@ -104,42 +106,32 @@ public class DataRepositoryCompletionProcessor implements CompletionProvider {
}
private DataRepositoryDefinition getDataRepositoryDefinition(ClassDeclaration declaration, FullyQualified type) {
if (type != null) {
// interface analysis
List<FullyQualified> interfaces = type.getInterfaces();
for (FullyQualified resolvedInterface : interfaces) {
String simplifiedType = resolvedInterface.getFullyQualifiedName();
if (Constants.REPOSITORY_TYPE.equals(simplifiedType)) {
DomainType domainType = null;
// TODO Fix for OR AST
// if (resolvedInterface.isParameterizedType()) {
// ITypeBinding[] typeParameters = resolvedInterface.getTypeArguments();
// if (typeParameters != null && typeParameters.length > 0) {
// domainType = new DomainType(typeParameters[0]);
// }
// }
return createDataRepositoryDefinitionFromType(domainType);
}
else {
DataRepositoryDefinition repo = getDataRepositoryDefinition(declaration, resolvedInterface);
if (repo != null) {
return repo;
}
}
if (type != null && TypeUtils.isAssignableTo(Constants.REPOSITORY_TYPE, type)) {
JavaType domainType = getFirstParameterType(type);
return new DataRepositoryDefinition(domainType instanceof FullyQualified ? new DomainType((FullyQualified) domainType) : null);
}
return null;
}
private JavaType getFirstParameterType(FullyQualified type) {
if (type instanceof Parameterized) {
List<JavaType> params = ((Parameterized) type).getTypeParameters();
if (!params.isEmpty()) {
return params.get(0);
}
// super type analysis
FullyQualified superclass = type.getSupertype();
if (superclass != null) {
return getDataRepositoryDefinition(declaration, superclass);
}
for (FullyQualified i : type.getInterfaces()) {
JavaType parameType = getFirstParameterType(i);
if (parameType != null) {
return parameType;
}
}
@Nullable
FullyQualified superclass = type.getSupertype();
if (superclass != null) {
return getFirstParameterType(superclass);
}
return null;
}
private DataRepositoryDefinition createDataRepositoryDefinitionFromType(DomainType domainType) {
return new DataRepositoryDefinition(domainType);
}
}

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2018 Pivotal, Inc.
* Copyright (c) 2018, 2022 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -13,8 +13,9 @@ package org.springframework.ide.vscode.boot.java.data;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.openrewrite.java.tree.JavaType;
import org.openrewrite.java.tree.JavaType.FullyQualified;
import org.openrewrite.java.tree.JavaType.Method;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
@@ -35,22 +36,25 @@ public class DomainType {
this.simpleName = simpleName;
}
public DomainType(ITypeBinding typeBinding) {
this.packageName = typeBinding.getPackage().getName();
this.fullName = typeBinding.getQualifiedName();
this.simpleName = typeBinding.getName();
public DomainType(FullyQualified type) {
this.packageName = type.getPackageName();
this.fullName = type.getFullyQualifiedName();
this.simpleName = type.getClassName();
this.properties = Suppliers.memoize(() -> {
if (!this.packageName.startsWith("java")) {
IMethodBinding[] methods = typeBinding.getDeclaredMethods();
if (methods != null && methods.length > 0) {
List<Method> methods = type.getMethods();
if (methods != null && !methods.isEmpty()) {
List<DomainProperty> properties = new ArrayList<>();
for (IMethodBinding method : methods) {
for (Method method : methods) {
String methodName = method.getName();
if (methodName != null && methodName.startsWith("get")) {
String propertyName = methodName.substring(3);
properties.add(new DomainProperty(propertyName, new DomainType(method.getReturnType())));
JavaType returnType = method.getReturnType();
if (returnType instanceof FullyQualified) {
properties.add(new DomainProperty(propertyName, new DomainType((FullyQualified) returnType)));
}
}
}
return (DomainProperty[]) properties.toArray(new DomainProperty[properties.size()]);

View File

@@ -139,24 +139,22 @@ public abstract class AbstractSourceLinks implements SourceLinks {
if (cu == null) {
return null;
}
int[] values = new int[] {0, -1};
AtomicReference<Range> range = new AtomicReference<>();
int lastDotIndex = fqName.lastIndexOf('.');
String packageName = fqName.substring(0, lastDotIndex);
String typeName = fqName.substring(lastDotIndex + 1);
if (packageName.equals(TypeUtils.asFullyQualified(cu.getPackageDeclaration().getExpression().getType()).getFullyQualifiedName())) {
if (packageName.equals(cu.getPackageDeclaration().getExpression().printTrimmed())) {
Stack<String> visitedType = new Stack<>();
new JavaIsoVisitor<>() {
public org.openrewrite.java.tree.J.ClassDeclaration visitClassDeclaration(org.openrewrite.java.tree.J.ClassDeclaration classDecl, Object p) {
String fqName = classDecl.getType().getFullyQualifiedName();
visitedType.push(fqName);
visitedType.push(classDecl.getSimpleName());
if (range.get() == null) {
if (String.join("$", visitedType.toArray(new String[visitedType.size()])).equals(typeName)) {
range.set(classDecl.getName().getMarkers().findFirst(Range.class).orElseThrow());
}
}
if (values[1] < 0) {
if (range.get() == null) {
return super.visitClassDeclaration(classDecl, visitedType);
} else {
return classDecl;

View File

@@ -49,7 +49,7 @@ public class VSCodeSourceLinks extends AbstractSourceLinks {
sb.append('#');
sb.append(line);
sb.append(',');
sb.append(column + 1); // 1-based columns?
sb.append(column);
return sb.toString();
}
}

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2017 Pivotal, Inc.
* Copyright (c) 2017, 2022 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -11,10 +11,12 @@
package org.springframework.ide.vscode.boot.java.snippets;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.J.Block;
import org.openrewrite.java.tree.J.ClassDeclaration;
import org.springframework.ide.vscode.boot.java.utils.ORAstUtils;
public interface JavaSnippetContext {
JavaSnippetContext BOOT_MEMBERS = (node) -> node instanceof ClassDeclaration;
JavaSnippetContext BOOT_MEMBERS = (node) -> node instanceof Block && ORAstUtils.getParent(node) instanceof ClassDeclaration;
boolean appliesTo(J node);
}

View File

@@ -27,7 +27,6 @@ import org.springframework.ide.vscode.boot.bootiful.HoverTestConf;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.util.text.LanguageId;
import org.springframework.ide.vscode.languageserver.testharness.Editor;
import org.springframework.ide.vscode.languageserver.testharness.TestAsserts;
import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness;
import org.springframework.ide.vscode.project.harness.ProjectsHarness;
import org.springframework.test.context.junit4.SpringRunner;

View File

@@ -0,0 +1,130 @@
/*******************************************************************************
* Copyright (c) 2022 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.requestmapping.test;
import static org.junit.Assert.assertEquals;
import java.io.InputStream;
import java.util.List;
import org.apache.commons.io.IOUtils;
import org.eclipse.lsp4j.CompletionItem;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Import;
import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest;
import org.springframework.ide.vscode.boot.bootiful.HoverTestConf;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.util.text.LanguageId;
import org.springframework.ide.vscode.languageserver.testharness.Editor;
import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness;
import org.springframework.ide.vscode.project.harness.ProjectsHarness;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@BootLanguageServerTest
@Import(HoverTestConf.class)
public class RequestMappingSnippetTests {
@Autowired private BootLanguageServerHarness harness;
private Editor editor;
@Before
public void setup() throws Exception {
IJavaProject testProject = ProjectsHarness.INSTANCE.mavenProject("test-request-mapping-live-hover");
harness.useProject(testProject);
harness.intialize(null);
}
@Test
public void getMapping() throws Exception {
prepareCase("Get<*>");
assertOneSnippet("package example;\n"
+ "\n"
+ "import org.springframework.stereotype.Controller;\n"
+ "import org.springframework.web.bind.annotation.DeleteMapping;\n"
+ "import org.springframework.web.bind.annotation.GetMapping;\n"
+ "import org.springframework.web.bind.annotation.PathVariable;\n"
+ "import org.springframework.web.bind.annotation.PostMapping;\n"
+ "import org.springframework.web.bind.annotation.PutMapping;\n"
+ "import org.springframework.web.bind.annotation.RequestBody;\n"
+ "import org.springframework.web.bind.annotation.RequestMapping;\n"
+ "import org.springframework.web.bind.annotation.ResponseBody;\n"
+ "\n"
+ "/** Boot Java - Test Completion */\n"
+ "@Controller\n"
+ "public class RestApi {\n"
+ "\n"
+ "@GetMapping(value=\"${1:path}\")\n"
+ "public ${2:SomeData} ${3:getMethodName}(@RequestParam ${4:String} ${5:param}) {\n"
+ " return new ${2:SomeData}($0);\n"
+ "}\n"
+ "<*>\n"
+ "\n"
+ "\n"
+ " @RequestMapping(\"/hello\")\n"
+ " @ResponseBody\n"
+ " public String hello() {\n"
+ " return \"Hello there!\";\n"
+ " }\n"
+ " \n"
+ " \n"
+ " @RequestMapping(\"/goodbye\")\n"
+ " @ResponseBody\n"
+ " public String goodbye() {\n"
+ " return \"Good bye\";\n"
+ " }\n"
+ "\n"
+ " @GetMapping(\"/person/{name}\")\n"
+ " public String getMapping(@PathVariable String name) {\n"
+ " return \"Hello \" + name;\n"
+ " }\n"
+ "\n"
+ " @DeleteMapping(\"/delete/{id}\")\n"
+ " public String removeMe(@PathVariable int id) {\n"
+ " System.out.println(\"You are removed: \" + id);\n"
+ " return \"Done\";\n"
+ " }\n"
+ "\n"
+ " @PostMapping(\"/postHello\")\n"
+ " public String postMethod(@RequestBody String name) {\n"
+ " System.out.println(\"Posted hello: \" + name);\n"
+ " return name;\n"
+ " }\n"
+ "\n"
+ " @PutMapping(\"/put/{id}\")\n"
+ " public String putMethod(@PathVariable int id, @RequestBody String name) {\n"
+ " System.out.println(\"Added \" + name + \" with ID: \" + id);\n"
+ " return name;\n"
+ " }\n"
+ "}\n"
+ "");
}
private void prepareCase(String prefix) throws Exception {
InputStream resource = this.getClass().getResourceAsStream("/test-projects/test-request-mapping-live-hover/src/main/java/example/RestApi.java");
String content = IOUtils.toString(resource);
content = content.replace("class RestApi {", "class RestApi {\n\n" + prefix);
editor = new Editor(harness, content, LanguageId.JAVA);
}
private void assertOneSnippet(String expected) throws Exception {
List<CompletionItem> completions = editor.getCompletions();
assertEquals(completions.size(), 1);
Editor clonedEditor = editor.clone();
clonedEditor.apply(completions.get(0));
assertEquals(expected, clonedEditor.getText());
}
}