diff --git a/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/LanguageServerHarness.java b/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/LanguageServerHarness.java index 31932a0f5..2aacea7c3 100644 --- a/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/LanguageServerHarness.java +++ b/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/LanguageServerHarness.java @@ -742,6 +742,23 @@ public class LanguageServerHarness { assertEquals(expect.toString(), actual.toString()); } + public void assertOneOfCompletions(String textBefore, String expectTextAfter) throws Exception { + Editor editor = newEditor(textBefore); + StringBuilder actualCompletions = new StringBuilder(); + + List completions = editor.getCompletions(); + for (CompletionItem ci : completions) { + editor = newEditor(textBefore); + editor.apply(ci); + if (editor.getText().equals(expectTextAfter)) { + return; + } + actualCompletions.append(editor.getText()); + actualCompletions.append("\n-------------------\n"); + } + fail("Not found expected proposal completion in:\n" + actualCompletions); + } + public void assertCompletionDisplayString(String editorContents, String expected) throws Exception { Editor editor = newEditor(editorContents); CompletionItem completion = editor.getFirstCompletion(); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DomainType.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DomainType.java index 1fd2f6998..80af93231 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DomainType.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DomainType.java @@ -20,6 +20,8 @@ import java.util.Set; import org.eclipse.jdt.core.dom.IMethodBinding; import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.IVariableBinding; +import org.springframework.ide.vscode.commons.util.StringUtil; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; @@ -58,38 +60,44 @@ public class DomainType extends SimpleType { private List calculateDomainProperties(ITypeBinding typeBinding) { if (!getPackageName().startsWith("java")) { - IMethodBinding[] methods = typeBinding.getDeclaredMethods(); - if (methods != null && methods.length > 0) { - List properties = new ArrayList<>(); + List properties = new ArrayList<>(); + if (typeBinding.isRecord()) { + for (IVariableBinding f : typeBinding.getDeclaredFields()) { + properties.add(new DomainProperty(StringUtil.upCaseFirstChar(f.getName()), new DomainType(f.getType()))); + } + } else { + IMethodBinding[] methods = typeBinding.getDeclaredMethods(); + if (methods != null && methods.length > 0) { - for (IMethodBinding method : methods) { - String methodName = method.getName(); - if (methodName != null) { - String propertyName = null; - if (methodName.startsWith("get")) { - propertyName = methodName.substring(3); - } - else if (methodName.startsWith("is")) { - propertyName = methodName.substring(2); - } - if (propertyName != null) { - properties.add(new DomainProperty(propertyName, new DomainType(method.getReturnType()))); + for (IMethodBinding method : methods) { + String methodName = method.getName(); + if (methodName != null) { + String propertyName = null; + if (methodName.startsWith("get")) { + propertyName = methodName.substring(3); + } + else if (methodName.startsWith("is")) { + propertyName = methodName.substring(2); + } + if (propertyName != null) { + properties.add(new DomainProperty(propertyName, new DomainType(method.getReturnType()))); + } } } - } - - if (typeBinding.getSuperclass() != null) { - properties.addAll(calculateDomainProperties(typeBinding.getSuperclass())); - } - - if (typeBinding.getInterfaces() != null) { - for (ITypeBinding si : typeBinding.getInterfaces()) { - properties.addAll(calculateDomainProperties(si)); + + if (typeBinding.getSuperclass() != null) { + properties.addAll(calculateDomainProperties(typeBinding.getSuperclass())); } + + if (typeBinding.getInterfaces() != null) { + for (ITypeBinding si : typeBinding.getInterfaces()) { + properties.addAll(calculateDomainProperties(si)); + } + } + } - - return properties; } + return properties; } return Collections.emptyList(); } diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryCompletionProcessorTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryCompletionProcessorTest.java index f82a591e5..87d23f2ce 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryCompletionProcessorTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryCompletionProcessorTest.java @@ -114,6 +114,76 @@ public class DataRepositoryCompletionProcessorTest { """); } + @Test + void recordEntity() throws Exception { + + harness.assertOneOfCompletions(""" + package org.test; + + import org.springframework.data.repository.CrudRepository; + import javax.persistence.Id; + + public class Pets { + + public record Pet(@Id Long id, String name) {} + + + public interface PetsRepositoryForCompletions extends CrudRepository { + findBy<*> + } + } + """, """ + package org.test; + + import org.springframework.data.repository.CrudRepository; + import javax.persistence.Id; + + public class Pets { + + public record Pet(@Id Long id, String name) {} + + + public interface PetsRepositoryForCompletions extends CrudRepository { + findByName<*> + } + } + """); + + harness.assertOneOfCompletions(""" + package org.test; + + import org.springframework.data.repository.CrudRepository; + import javax.persistence.Id; + + public class Pets { + + public record Pet(@Id Long id, String name) {} + + + public interface PetsRepositoryForCompletions extends CrudRepository { + findBy<*> + } + } + """, """ + package org.test; + + import org.springframework.data.repository.CrudRepository; + import javax.persistence.Id; + import java.util.List; + + + public class Pets { + + public record Pet(@Id Long id, String name) {} + + + public interface PetsRepositoryForCompletions extends CrudRepository { + List findByName(String name);<*> + } + } + """); + } + @Test void prefixSensitiveMethodCompletionWithImports_1() throws Exception { checkCompletionResult("findByResponsibleEmployeeAndLastName", "findByResponsibleEmployeeAndLastName", """ @@ -251,7 +321,6 @@ public class DataRepositoryCompletionProcessorTest { prepareCase("{\n}", "{\n\t" + prefix + "<*>\n}"); List completions = editor.getCompletions(); - int i = 0; for (CompletionItem foundCompletion : completions) { if (foundCompletion.getLabel().contains(completionLabel)) { Editor clonedEditor = editor.clone();