Support type import for spring data repository completions
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016, 2020 Pivotal, Inc.
|
||||
* Copyright (c) 2016, 2023 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
|
||||
@@ -494,6 +494,18 @@ public class Editor {
|
||||
selectionEnd += insertText.length();
|
||||
setRawText(newText);
|
||||
}
|
||||
|
||||
// Experimental additional edit support. Only makes good sense when it is above the text being edited
|
||||
if (completion.getAdditionalTextEdits() != null) {
|
||||
for (TextEdit te : completion.getAdditionalTextEdits()) {
|
||||
String replaceWith = te.getNewText();
|
||||
Range rng = te.getRange();
|
||||
int start = doc.toOffset(rng.getStart());
|
||||
int end = doc.toOffset(rng.getEnd());
|
||||
replaceText(start, end, replaceWith);
|
||||
selectionStart = selectionEnd = selectionStart - (end - start) + replaceWith.length();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getInsertText(CompletionItem completion) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2018 Pivotal, Inc.
|
||||
* Copyright (c) 2018, 2023 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
|
||||
@@ -61,7 +61,7 @@ public class DataRepositoryCompletionProcessor implements CompletionProvider {
|
||||
// ignore if there is a problem computing the prefix, continue without prefix
|
||||
}
|
||||
for(DataRepositoryCompletionProvider provider : completionProviders){
|
||||
provider.addProposals(completions, doc, offset, prefix, repo);
|
||||
provider.addProposals(completions, doc, offset, prefix, repo, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,14 +90,7 @@ public class DataRepositoryCompletionProcessor implements CompletionProvider {
|
||||
}
|
||||
|
||||
if (Constants.REPOSITORY_TYPE.equals(simplifiedType)) {
|
||||
DomainType domainType = null;
|
||||
if (resolvedInterface.isParameterizedType()) {
|
||||
ITypeBinding[] typeParameters = resolvedInterface.getTypeArguments();
|
||||
if (typeParameters != null && typeParameters.length > 0) {
|
||||
domainType = new DomainType(typeParameters[0]);
|
||||
}
|
||||
}
|
||||
return createDataRepositoryDefinitionFromType(domainType);
|
||||
return createDataRepositoryDefinitionFromType(resolvedInterface);
|
||||
}
|
||||
else {
|
||||
DataRepositoryDefinition repo = getDataRepositoryDefinition(type, resolvedInterface);
|
||||
@@ -116,7 +109,14 @@ public class DataRepositoryCompletionProcessor implements CompletionProvider {
|
||||
return null;
|
||||
}
|
||||
|
||||
private DataRepositoryDefinition createDataRepositoryDefinitionFromType(DomainType domainType) {
|
||||
return new DataRepositoryDefinition(domainType);
|
||||
private DataRepositoryDefinition createDataRepositoryDefinitionFromType(ITypeBinding resolvedInterface) {
|
||||
DomainType domainType = null;
|
||||
if (resolvedInterface.isParameterizedType()) {
|
||||
ITypeBinding[] typeParameters = resolvedInterface.getTypeArguments();
|
||||
if (typeParameters != null && typeParameters.length > 0) {
|
||||
domainType = new DomainType(typeParameters[0]);
|
||||
}
|
||||
}
|
||||
return new DataRepositoryDefinition(new SimpleType(resolvedInterface),domainType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2018 Pivotal, Inc.
|
||||
* Copyright (c) 2018, 2023 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
|
||||
@@ -15,15 +15,20 @@ package org.springframework.ide.vscode.boot.java.data;
|
||||
*/
|
||||
public class DataRepositoryDefinition {
|
||||
|
||||
private final SimpleType type;
|
||||
private final DomainType domainType;
|
||||
|
||||
public DataRepositoryDefinition(DomainType domainType) {
|
||||
super();
|
||||
public DataRepositoryDefinition(SimpleType type, DomainType domainType) {
|
||||
this.type = type;
|
||||
this.domainType = domainType;
|
||||
}
|
||||
|
||||
public DomainType getDomainType() {
|
||||
return domainType;
|
||||
}
|
||||
|
||||
public SimpleType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,9 +12,11 @@ package org.springframework.ide.vscode.boot.java.data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.core.dom.IMethodBinding;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
@@ -25,36 +27,37 @@ import com.google.common.base.Suppliers;
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class DomainType {
|
||||
public class DomainType extends SimpleType {
|
||||
|
||||
private final String packageName;
|
||||
private final String fullName;
|
||||
private final String simpleName;
|
||||
private Supplier<DomainProperty[]> properties;
|
||||
|
||||
public DomainType(String packageName, String fullName, String simpleName) {
|
||||
this.packageName = packageName;
|
||||
this.fullName = fullName;
|
||||
this.simpleName = simpleName;
|
||||
}
|
||||
private Set<String> usedTypes;
|
||||
|
||||
public DomainType(ITypeBinding typeBinding) {
|
||||
if (typeBinding.getPackage() == null) {
|
||||
this.packageName = "";
|
||||
} else {
|
||||
this.packageName = typeBinding.getPackage().getName();
|
||||
}
|
||||
this.fullName = typeBinding.getQualifiedName();
|
||||
this.simpleName = typeBinding.getName();
|
||||
super(typeBinding);
|
||||
|
||||
this.properties = Suppliers.memoize(() -> {
|
||||
List<DomainProperty> domainProps = calculateDomainProperties(typeBinding);
|
||||
return domainProps.toArray(new DomainProperty[domainProps.size()]);
|
||||
});
|
||||
|
||||
this.usedTypes = new HashSet<>();
|
||||
fillImports(typeBinding);
|
||||
}
|
||||
|
||||
private void fillImports(ITypeBinding binding) {
|
||||
if (binding != binding.getErasure()) {
|
||||
fillImports(binding.getErasure());
|
||||
for (ITypeBinding ta : binding.getTypeArguments()) {
|
||||
fillImports(ta);
|
||||
}
|
||||
} else if (binding.isClass() || binding.isInterface() || binding.isEnum() || binding.isRecord()) {
|
||||
String fqName = binding.getQualifiedName();
|
||||
usedTypes.add(fqName);
|
||||
}
|
||||
}
|
||||
|
||||
private List<DomainProperty> calculateDomainProperties(ITypeBinding typeBinding) {
|
||||
if (!this.packageName.startsWith("java")) {
|
||||
if (!getPackageName().startsWith("java")) {
|
||||
IMethodBinding[] methods = typeBinding.getDeclaredMethods();
|
||||
if (methods != null && methods.length > 0) {
|
||||
List<DomainProperty> properties = new ArrayList<>();
|
||||
@@ -91,18 +94,6 @@ public class DomainType {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public String getPackageName() {
|
||||
return packageName;
|
||||
}
|
||||
|
||||
public String getFullName() {
|
||||
return fullName;
|
||||
}
|
||||
|
||||
public String getSimpleName() {
|
||||
return simpleName;
|
||||
}
|
||||
|
||||
public DomainProperty[] getProperties() {
|
||||
return properties.get();
|
||||
}
|
||||
@@ -114,5 +105,9 @@ public class DomainType {
|
||||
}
|
||||
return propertiesByName;
|
||||
}
|
||||
|
||||
public Set<String> getUsedTypes() {
|
||||
return usedTypes;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2018, 2019 Pivotal, Inc.
|
||||
* Copyright (c) 2018, 2023 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
|
||||
@@ -41,7 +41,7 @@ public class FindByCompletionProposal implements ICompletionProposal {
|
||||
this.triggerNextCompletion = triggerNextCompletion;
|
||||
}
|
||||
|
||||
public static ICompletionProposal createProposal(int offset, CompletionItemKind completionItemKind, String prefix, String label, String completion, boolean triggerNextCompletion) {
|
||||
public static ICompletionProposal createProposal(int offset, CompletionItemKind completionItemKind, String prefix, String label, String completion, boolean triggerNextCompletion, Optional<DocumentEdits> additionalEdits) {
|
||||
DocumentEdits edits = new DocumentEdits(null, false);
|
||||
String filter = label;
|
||||
if (prefix != null && label.startsWith(prefix)) {
|
||||
@@ -55,8 +55,7 @@ public class FindByCompletionProposal implements ICompletionProposal {
|
||||
edits.insert(offset, completion);
|
||||
}
|
||||
|
||||
DocumentEdits additionalEdits = new DocumentEdits(null, false);
|
||||
return new FindByCompletionProposal(label, completionItemKind, edits, null, null, Optional.of(additionalEdits), filter, triggerNextCompletion);
|
||||
return new FindByCompletionProposal(label, completionItemKind, edits, null, null, additionalEdits, filter, triggerNextCompletion);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2023 VMware, 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:
|
||||
* VMware, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.data;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
|
||||
public class SimpleType {
|
||||
|
||||
private final String packageName;
|
||||
private final String fullName;
|
||||
private final String simpleName;
|
||||
|
||||
public SimpleType(ITypeBinding typeBinding) {
|
||||
if (typeBinding.getPackage() == null) {
|
||||
this.packageName = "";
|
||||
} else {
|
||||
this.packageName = typeBinding.getPackage().getName();
|
||||
}
|
||||
this.fullName = typeBinding.getQualifiedName();
|
||||
this.simpleName = typeBinding.getName();
|
||||
}
|
||||
|
||||
public String getPackageName() {
|
||||
return packageName;
|
||||
}
|
||||
|
||||
public String getFullName() {
|
||||
return fullName;
|
||||
}
|
||||
|
||||
public String getSimpleName() {
|
||||
return simpleName;
|
||||
}
|
||||
|
||||
public boolean shouldImportType(String fqName) {
|
||||
if (fqName.startsWith("java.lang.")) {
|
||||
return false;
|
||||
}
|
||||
if (fqName.startsWith(packageName) && fqName.length() >= packageName.length() + 2 // +2: one for '.', second for capital letter
|
||||
&& fqName.charAt(packageName.length()) == '.' && Character.isUpperCase(fqName.charAt(packageName.length() + 1))) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -12,6 +12,7 @@ package org.springframework.ide.vscode.boot.java.data.providers;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.springframework.ide.vscode.boot.java.data.DataRepositoryDefinition;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
@@ -22,6 +23,6 @@ import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
*/
|
||||
public interface DataRepositoryCompletionProvider {
|
||||
|
||||
void addProposals(Collection<ICompletionProposal> completions, IDocument doc, int offset, String prefix, DataRepositoryDefinition repo);
|
||||
void addProposals(Collection<ICompletionProposal> completions, IDocument doc, int offset, String prefix, DataRepositoryDefinition repo, ASTNode node);
|
||||
|
||||
}
|
||||
|
||||
@@ -11,7 +11,9 @@
|
||||
package org.springframework.ide.vscode.boot.java.data.providers;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.lsp4j.CompletionItemKind;
|
||||
import org.springframework.ide.vscode.boot.java.data.DataRepositoryDefinition;
|
||||
import org.springframework.ide.vscode.boot.java.data.FindByCompletionProposal;
|
||||
@@ -27,12 +29,12 @@ import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
public class DataRepositoryQueryStartCompletionProvider implements DataRepositoryCompletionProvider{
|
||||
|
||||
@Override
|
||||
public void addProposals(Collection<ICompletionProposal> completions, IDocument doc, int offset, String prefix, DataRepositoryDefinition repo) {
|
||||
public void addProposals(Collection<ICompletionProposal> completions, IDocument doc, int offset, String prefix, DataRepositoryDefinition repo, ASTNode node) {
|
||||
String localPrefix = DataRepositoryPrefixSensitiveCompletionProvider.findLastJavaIdentifierPart(prefix);
|
||||
for(QueryMethodSubject queryMethodSubject : QueryMethodSubject.QUERY_METHOD_SUBJECTS){
|
||||
String toInsert = queryMethodSubject.key() + "By";
|
||||
if(prefix == null || (toInsert.length() > localPrefix.length() && toInsert.startsWith(localPrefix)) || isOffsetAfterWhitespace(doc, offset)) {
|
||||
completions.add(FindByCompletionProposal.createProposal(offset, CompletionItemKind.Text, prefix, toInsert, toInsert, true));
|
||||
completions.add(FindByCompletionProposal.createProposal(offset, CompletionItemKind.Text, prefix, toInsert, toInsert, true, Optional.empty()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,17 +11,26 @@
|
||||
package org.springframework.ide.vscode.boot.java.data.providers;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.eclipse.lsp4j.CompletionItemKind;
|
||||
import org.springframework.ide.vscode.boot.java.data.DataRepositoryDefinition;
|
||||
import org.springframework.ide.vscode.boot.java.data.DomainProperty;
|
||||
import org.springframework.ide.vscode.boot.java.data.DomainType;
|
||||
import org.springframework.ide.vscode.boot.java.data.FindByCompletionProposal;
|
||||
import org.springframework.ide.vscode.boot.java.data.providers.prefixsensitive.DataRepositoryPrefixSensitiveCompletionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
|
||||
/**
|
||||
* Provides content assist proposals for querying by a single attribute in Spring Data repositories.
|
||||
* @author Martin Lippert
|
||||
@@ -30,16 +39,17 @@ public class DataRepositoryStandardCompletionProvider implements DataRepositoryC
|
||||
|
||||
private static final String FIND_BY = "findBy";
|
||||
|
||||
public void addProposals(Collection<ICompletionProposal> completions, IDocument doc, int offset, String prefix, DataRepositoryDefinition repo) {
|
||||
public void addProposals(Collection<ICompletionProposal> completions, IDocument doc, int offset, String prefix, DataRepositoryDefinition repo, ASTNode node) {
|
||||
if (FIND_BY.startsWith(DataRepositoryPrefixSensitiveCompletionProvider.findLastJavaIdentifierPart(prefix))) {
|
||||
DomainType domainType = repo.getDomainType();
|
||||
for (DomainProperty property : domainType.getPropertiesByName().values()) {
|
||||
completions.add(generateCompletionProposal(offset, prefix, repo, property));
|
||||
completions.add(generateCompletionProposal(offset, prefix, repo, property, doc, node));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ICompletionProposal generateCompletionProposal(int offset, String prefix, DataRepositoryDefinition repoDef, DomainProperty domainProperty) {
|
||||
private ICompletionProposal generateCompletionProposal(int offset, String prefix, DataRepositoryDefinition repoDef, DomainProperty domainProperty, IDocument doc, ASTNode node) {
|
||||
Set<String> imprts = new HashSet<>();
|
||||
StringBuilder label = new StringBuilder();
|
||||
label.append("findBy");
|
||||
label.append(StringUtils.capitalize(domainProperty.getName()));
|
||||
@@ -50,16 +60,23 @@ public class DataRepositoryStandardCompletionProvider implements DataRepositoryC
|
||||
label.append(");");
|
||||
|
||||
StringBuilder completion = new StringBuilder();
|
||||
imprts.add(List.class.getName());
|
||||
completion.append("List<");
|
||||
completion.append(repoDef.getDomainType().getSimpleName());
|
||||
completion.append("> findBy");
|
||||
completion.append(StringUtils.capitalize(domainProperty.getName()));
|
||||
completion.append("(");
|
||||
completion.append(domainProperty.getType().getSimpleName());
|
||||
if (!Objects.equal(repoDef.getDomainType().getPackageName(), domainProperty.getType().getPackageName())) {
|
||||
imprts.addAll(domainProperty.getType().getUsedTypes().stream()
|
||||
.filter(repoDef.getType()::shouldImportType)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
completion.append(" ");
|
||||
completion.append(StringUtils.uncapitalize(domainProperty.getName()));
|
||||
completion.append(");");
|
||||
|
||||
return FindByCompletionProposal.createProposal(offset, CompletionItemKind.Method, prefix, label.toString(), completion.toString(), false);
|
||||
|
||||
return FindByCompletionProposal.createProposal(offset, CompletionItemKind.Method, prefix, label.toString(), completion.toString(), false, ASTUtils.getAdditionalEdit((CompilationUnit)node.getRoot(), imprts, doc));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,15 +19,18 @@ import java.util.List;
|
||||
* @author danthe1st
|
||||
*/
|
||||
public record QueryMethodSubject(
|
||||
String key, String returnType, boolean isTyped) {
|
||||
|
||||
String key, String returnType, String fqName) {
|
||||
|
||||
private static final String LIST = "List";
|
||||
private static final String LIST_FQ = "java.util.List";
|
||||
|
||||
public static final List<QueryMethodSubject> QUERY_METHOD_SUBJECTS = List.of(
|
||||
QueryMethodSubject.createCollectionSubject("find", "List"),
|
||||
QueryMethodSubject.createCollectionSubject("read", "List"),
|
||||
QueryMethodSubject.createCollectionSubject("get", "List"),
|
||||
QueryMethodSubject.createCollectionSubject("query", "List"),
|
||||
QueryMethodSubject.createCollectionSubject("search", "List"),
|
||||
QueryMethodSubject.createCollectionSubject("stream", "Streamable"),
|
||||
QueryMethodSubject.createCollectionSubject("find", LIST, LIST_FQ),
|
||||
QueryMethodSubject.createCollectionSubject("read", LIST, LIST_FQ),
|
||||
QueryMethodSubject.createCollectionSubject("get", LIST, LIST_FQ),
|
||||
QueryMethodSubject.createCollectionSubject("query", LIST, LIST_FQ),
|
||||
QueryMethodSubject.createCollectionSubject("search", LIST, LIST_FQ),
|
||||
QueryMethodSubject.createCollectionSubject("stream", "Streamable", "org.springframework.data.util.Streamable"),
|
||||
QueryMethodSubject.createPrimitiveSubject("exists", "boolean"),
|
||||
QueryMethodSubject.createPrimitiveSubject("count", "long"),
|
||||
QueryMethodSubject.createPrimitiveSubject("delete", "void"),
|
||||
@@ -35,10 +38,10 @@ public record QueryMethodSubject(
|
||||
);
|
||||
|
||||
private static QueryMethodSubject createPrimitiveSubject(String key, String primitive) {
|
||||
return new QueryMethodSubject(key, primitive, false);
|
||||
return new QueryMethodSubject(key, primitive, null);
|
||||
}
|
||||
private static QueryMethodSubject createCollectionSubject(String key, String collectionType) {
|
||||
return new QueryMethodSubject(key, collectionType, true);
|
||||
private static QueryMethodSubject createCollectionSubject(String key, String collectionTypeSimpleName, String collectionTypeFqName) {
|
||||
return new QueryMethodSubject(key, collectionTypeSimpleName, collectionTypeFqName);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,16 +11,21 @@
|
||||
package org.springframework.ide.vscode.boot.java.data.providers.prefixsensitive;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.eclipse.lsp4j.CompletionItemKind;
|
||||
import org.springframework.ide.vscode.boot.java.data.DataRepositoryDefinition;
|
||||
import org.springframework.ide.vscode.boot.java.data.DomainProperty;
|
||||
import org.springframework.ide.vscode.boot.java.data.DomainType;
|
||||
import org.springframework.ide.vscode.boot.java.data.FindByCompletionProposal;
|
||||
import org.springframework.ide.vscode.boot.java.data.providers.DataRepositoryCompletionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
@@ -33,7 +38,7 @@ import org.springframework.util.StringUtils;
|
||||
public class DataRepositoryPrefixSensitiveCompletionProvider implements DataRepositoryCompletionProvider {
|
||||
|
||||
@Override
|
||||
public void addProposals(Collection<ICompletionProposal> completions, IDocument doc, int offset, String prefix, DataRepositoryDefinition repoDef) {
|
||||
public void addProposals(Collection<ICompletionProposal> completions, IDocument doc, int offset, String prefix, DataRepositoryDefinition repoDef, ASTNode node) {
|
||||
String localPrefix = findLastJavaIdentifierPart(prefix);
|
||||
if (localPrefix == null) {
|
||||
return;
|
||||
@@ -42,7 +47,7 @@ public class DataRepositoryPrefixSensitiveCompletionProvider implements DataRepo
|
||||
if(parseResult != null && parseResult.performFullCompletion()){
|
||||
Map<String, DomainProperty> propertiesByName = repoDef.getDomainType().getPropertiesByName();
|
||||
if (isEndingWithProperty(parseResult, propertiesByName) || isEndingWithPredicateKeyWord(localPrefix, propertiesByName)) {
|
||||
addMethodCompletionProposal(completions, offset, repoDef, localPrefix, prefix, parseResult, propertiesByName);
|
||||
addMethodCompletionProposal(completions, offset, repoDef, localPrefix, prefix, parseResult, propertiesByName, node, doc);
|
||||
}
|
||||
|
||||
if (parseResult.lastWord() == null || !propertiesByName.containsKey(parseResult.lastWord())) {
|
||||
@@ -115,13 +120,17 @@ public class DataRepositoryPrefixSensitiveCompletionProvider implements DataRepo
|
||||
}
|
||||
}
|
||||
|
||||
private void addMethodCompletionProposal(Collection<ICompletionProposal> completions, int offset, DataRepositoryDefinition repoDef, String localPrefix, String fullPrefix, DataRepositoryMethodNameParseResult parseResult, Map<String, DomainProperty> propertiesByName) {
|
||||
private void addMethodCompletionProposal(Collection<ICompletionProposal> completions, int offset, DataRepositoryDefinition repoDef, String localPrefix, String fullPrefix, DataRepositoryMethodNameParseResult parseResult, Map<String, DomainProperty> propertiesByName, ASTNode node, IDocument doc) {
|
||||
String methodName = localPrefix;
|
||||
DocumentEdits edits = new DocumentEdits(null, false);
|
||||
String signature = buildSignature(methodName, propertiesByName, parseResult);
|
||||
Set<String> imports = new HashSet<>();
|
||||
String signature = buildSignature(methodName, propertiesByName, parseResult, imports, repoDef);
|
||||
StringBuilder newText = new StringBuilder();
|
||||
newText.append(parseResult.subjectType().returnType());
|
||||
if (parseResult.subjectType().isTyped()) {
|
||||
if (parseResult.subjectType().fqName() != null && repoDef.getType().shouldImportType(parseResult.subjectType().fqName())) {
|
||||
imports.add(parseResult.subjectType().fqName());
|
||||
}
|
||||
if (parseResult.subjectType().fqName() != null) {
|
||||
newText.append("<");
|
||||
newText.append(repoDef.getDomainType().getSimpleName());
|
||||
newText.append(">");
|
||||
@@ -132,8 +141,8 @@ public class DataRepositoryPrefixSensitiveCompletionProvider implements DataRepo
|
||||
newText.append(";");
|
||||
int replaceStart = calculateReplaceOffset(offset, localPrefix, fullPrefix, returnType);
|
||||
edits.replace(replaceStart, offset, newText.toString());
|
||||
DocumentEdits additionalEdits = new DocumentEdits(null, false);
|
||||
ICompletionProposal proposal = new FindByCompletionProposal(signature, CompletionItemKind.Method, edits, null, null, Optional.of(additionalEdits), signature, false);
|
||||
Optional<DocumentEdits> additionalEdits = ASTUtils.getAdditionalEdit((CompilationUnit) node.getRoot(), imports, doc);
|
||||
ICompletionProposal proposal = new FindByCompletionProposal(signature, CompletionItemKind.Method, edits, null, null, additionalEdits, signature, false);
|
||||
completions.add(proposal);
|
||||
}
|
||||
|
||||
@@ -147,7 +156,7 @@ public class DataRepositoryPrefixSensitiveCompletionProvider implements DataRepo
|
||||
return replaceStart;
|
||||
}
|
||||
|
||||
private String buildSignature(String methodName, Map<String, DomainProperty> properties, DataRepositoryMethodNameParseResult parseResult) {
|
||||
private String buildSignature(String methodName, Map<String, DomainProperty> properties, DataRepositoryMethodNameParseResult parseResult, Set<String> imports, DataRepositoryDefinition repoDef) {
|
||||
StringBuilder signatureBuilder = new StringBuilder();
|
||||
signatureBuilder.append(methodName);
|
||||
signatureBuilder.append("(");
|
||||
@@ -157,8 +166,13 @@ public class DataRepositoryPrefixSensitiveCompletionProvider implements DataRepo
|
||||
DomainType type = findExpressionType(properties, param);
|
||||
if (type == null) {
|
||||
signatureBuilder.append("Object");
|
||||
}else {
|
||||
} else {
|
||||
signatureBuilder.append(type.getSimpleName());
|
||||
for (String im : type.getUsedTypes()) {
|
||||
if (repoDef.getType().shouldImportType(im)) {
|
||||
imports.add(im);
|
||||
}
|
||||
}
|
||||
}
|
||||
signatureBuilder.append(" ");
|
||||
signatureBuilder.append(StringUtils.uncapitalize(param));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2019 Pivotal, Inc.
|
||||
* Copyright (c) 2017, 2023 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
|
||||
@@ -20,6 +20,7 @@ import java.util.stream.Stream;
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.ArrayInitializer;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.eclipse.jdt.core.dom.Expression;
|
||||
import org.eclipse.jdt.core.dom.IBinding;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
@@ -37,8 +38,11 @@ import org.eclipse.lsp4j.Range;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.boot.java.Annotations;
|
||||
import org.springframework.ide.vscode.boot.java.jdt.imports.ImportRewrite;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
|
||||
import org.springframework.ide.vscode.commons.util.CollectorUtil;
|
||||
import org.springframework.ide.vscode.commons.util.text.DocumentRegion;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@@ -302,5 +306,17 @@ public class ASTUtils {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Optional<DocumentEdits> getAdditionalEdit(CompilationUnit cu, Collection<String> imprts, IDocument doc) {
|
||||
ImportRewrite rewrite = ImportRewrite.create(cu, true);
|
||||
|
||||
for (String imprt : imprts) {
|
||||
rewrite.addImport(imprt);
|
||||
}
|
||||
|
||||
DocumentEdits edit = rewrite.createEdit(doc);
|
||||
|
||||
return edit != null ? Optional.of(edit) : Optional.empty();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ import java.util.List;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
import org.eclipse.lsp4j.CompletionItem;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -40,7 +42,7 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
@BootLanguageServerTest
|
||||
@Import(HoverTestConf.class)
|
||||
public class DataRepositoryCompletionProcessorTest {
|
||||
|
||||
|
||||
@Autowired private BootLanguageServerHarness harness;
|
||||
private Editor editor;
|
||||
|
||||
@@ -80,7 +82,85 @@ public class DataRepositoryCompletionProcessorTest {
|
||||
"searchBy",
|
||||
"streamBy");
|
||||
}
|
||||
|
||||
@Test
|
||||
void assertCompletionWithImportDifferentAndSamePackage() throws Exception {
|
||||
checkCompletionResult("", "findByResponsibleEmployee", """
|
||||
package org.test;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import java.util.List;
|
||||
import org.test.model.Employee;
|
||||
|
||||
|
||||
public interface TestCustomerRepositoryForCompletions extends CrudRepository<Customer, Long> {
|
||||
List<Customer> findByResponsibleEmployee(Employee responsibleEmployee);<*>
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void assertCompletionWithImportFromJavaLang() throws Exception {
|
||||
checkCompletionResult("", "findById", """
|
||||
package org.test;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public interface TestCustomerRepositoryForCompletions extends CrudRepository<Customer, Long> {
|
||||
List<Customer> findById(Long id);<*>
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void prefixSensitiveMethodCompletionWithImports_1() throws Exception {
|
||||
checkCompletionResult("findByResponsibleEmployeeAndLastName", "findByResponsibleEmployeeAndLastName", """
|
||||
package org.test;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.test.model.Employee;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public interface TestCustomerRepositoryForCompletions extends CrudRepository<Customer, Long> {
|
||||
List<Customer> findByResponsibleEmployeeAndLastName(Employee responsibleEmployee, String lastName);<*>
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void prefixSensitiveMethodCompletionWithImports_2() throws Exception {
|
||||
checkCompletionResult("streamByResponsibleEmployeeAndLastName", "streamByResponsibleEmployeeAndLastName", """
|
||||
package org.test;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.test.model.Employee;
|
||||
import org.springframework.data.util.Streamable;
|
||||
|
||||
|
||||
public interface TestCustomerRepositoryForCompletions extends CrudRepository<Customer, Long> {
|
||||
Streamable<Customer> streamByResponsibleEmployeeAndLastName(Employee responsibleEmployee, String lastName);<*>
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void prefixSensitiveMethodCompletionWithImports_3() throws Exception {
|
||||
checkCompletionResult("countByResponsibleEmployee", "countByResponsibleEmployee", """
|
||||
package org.test;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.test.model.Employee;
|
||||
|
||||
|
||||
public interface TestCustomerRepositoryForCompletions extends CrudRepository<Customer, Long> {
|
||||
long countByResponsibleEmployee(Employee responsibleEmployee);<*>
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPrefixSensitiveCompletionsCompleteMethod() throws Exception {
|
||||
checkCompletions("findByFirstNameAndLastName", "List<Customer> findByFirstNameAndLastName(String firstName, String lastName);");
|
||||
@@ -166,6 +246,22 @@ public class DataRepositoryCompletionProcessorTest {
|
||||
"findByFirstNameAfter",
|
||||
"findByFirstNameAnd");
|
||||
}
|
||||
|
||||
private void checkCompletionResult(String prefix, String completionLabel, String result) throws Exception {
|
||||
prepareCase("{\n}", "{\n\t" + prefix + "<*>\n}");
|
||||
List<CompletionItem> completions = editor.getCompletions();
|
||||
|
||||
int i = 0;
|
||||
for (CompletionItem foundCompletion : completions) {
|
||||
if (foundCompletion.getLabel().contains(completionLabel)) {
|
||||
Editor clonedEditor = editor.clone();
|
||||
clonedEditor.apply(foundCompletion);
|
||||
assertEquals(result, clonedEditor.getText());
|
||||
return;
|
||||
}
|
||||
}
|
||||
fail("Didn't find the proposal with label: " + completionLabel);
|
||||
}
|
||||
|
||||
private void checkCompletions(String alredyPresent, String... expectedCompletions) throws Exception {
|
||||
prepareCase("{\n}", "{\n\t" + alredyPresent + "<*>");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2022 Pivotal, Inc.
|
||||
* Copyright (c) 2022, 2023 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
|
||||
@@ -60,6 +60,8 @@ public class RequestMappingSnippetTests {
|
||||
+ "import org.springframework.web.bind.annotation.RequestBody;\n"
|
||||
+ "import org.springframework.web.bind.annotation.RequestMapping;\n"
|
||||
+ "import org.springframework.web.bind.annotation.ResponseBody;\n"
|
||||
+ "import org.springframework.web.bind.annotation.RequestParam;\n"
|
||||
+ "\n"
|
||||
+ "\n"
|
||||
+ "/** Boot Java - Test Completion */\n"
|
||||
+ "@Controller\n"
|
||||
|
||||
@@ -6,6 +6,7 @@ import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.test.model.Employee;
|
||||
|
||||
@SpringBootApplication
|
||||
public class Application {
|
||||
|
||||
@@ -7,6 +7,9 @@ import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.ManyToOne;
|
||||
|
||||
import org.test.model.Employee;
|
||||
import org.test.model.Person;
|
||||
|
||||
@Entity
|
||||
public class Customer extends Person {
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// tag::sample[]
|
||||
package org.test;
|
||||
package org.test.model;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.test;
|
||||
package org.test.model;
|
||||
|
||||
public class Person {
|
||||
|
||||
Reference in New Issue
Block a user