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

completions for predicate keywords in Spring Data repositories #988

Closed
Closed
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 @@ -33,9 +33,9 @@ record DataRepositoryMethodNameParseResult(
*/
boolean performFullCompletion,
/**
* the last entered word, which completion options should be used for completing the expression.
* the last entered word (if it is not a keyword), which completion options should be used for completing the expression.
*
* e.g. {@code First} in {@code findByFirst} which could be completed to {@code findByFirstName}
* e.g. {@code First} in {@code findByFirst} which could be completed to {@code findByFirstName} or {@code null} if it is a keyword or non-existent
*/
String lastWord,
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@ class DataRepositoryMethodParser {

private static final Map<String, List<QueryPredicateKeywordInfo>> PREDICATE_KEYWORDS_GROUPED_BY_FIRST_WORD = QueryPredicateKeywordInfo.PREDICATE_KEYWORDS
.stream()
.collect(Collectors.groupingBy(info->{
return findFirstWord(info.keyword());
}));
.collect(Collectors.groupingBy(info -> findFirstWord(info.keyword())));

private final String prefix;
private final Map<String, List<DomainProperty>> propertiesGroupedByFirstWord;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,23 +47,56 @@ public void addProposals(Collection<ICompletionProposal> completions, IDocument
if (parseResult.lastWord() == null || !propertiesByName.containsKey(parseResult.lastWord())) {
addPropertyProposals(completions, offset, repoDef, parseResult);
}
addPredicateKeywordProposals(completions, offset, prefix, parseResult, propertiesByName);
}
}

private void addPropertyProposals(Collection<ICompletionProposal> completions, int offset, DataRepositoryDefinition repoDef, DataRepositoryMethodNameParseResult parseResult) {
for(DomainProperty property : repoDef.getDomainType().getProperties()){
String lastWord = parseResult.lastWord();
if (lastWord == null) {
lastWord = "";
private void addPredicateKeywordProposals(Collection<ICompletionProposal> completions, int offset, String prefix, DataRepositoryMethodNameParseResult parseResult, Map<String, DomainProperty> propertiesByName) {
String lastWord = findLastWordWithoutPrefixingProperty(parseResult, propertiesByName);
for (QueryPredicateKeywordInfo predicate : QueryPredicateKeywordInfo.PREDICATE_KEYWORDS){
if (parseResult.allowedKeywordTypes().contains(predicate.type())) {
createLastWordReplacementCompletion(completions, offset, parseResult, lastWord, predicate.keyword());
}
if (property.getName().startsWith(lastWord)) {
DocumentEdits edits = new DocumentEdits(null, false);
edits.replace(offset - lastWord.length(), offset, property.getName());
DocumentEdits additionalEdits = new DocumentEdits(null, false);
ICompletionProposal proposal = new FindByCompletionProposal(property.getName(), CompletionItemKind.Text, edits, "property " + property.getName(), null, Optional.of(additionalEdits), lastWord);
completions.add(proposal);
}
}

private String findLastWordWithoutPrefixingProperty(DataRepositoryMethodNameParseResult parseResult, Map<String, DomainProperty> propertiesByName) {
String lastWord = parseResult.lastWord();
if (lastWord == null) {
return "";
}
if (propertiesByName.containsKey(lastWord)) {
return "";
}
for (int i = lastWord.length() - 1; i >= 0; i--) {
if (Character.isUpperCase(lastWord.charAt(i))) {
String substring = lastWord.substring(0,i);
if (propertiesByName.containsKey(substring)) {
return lastWord.substring(i);
}
}
}
return lastWord;
}

private void addPropertyProposals(Collection<ICompletionProposal> completions, int offset, DataRepositoryDefinition repoDef, DataRepositoryMethodNameParseResult parseResult) {
for(DomainProperty property : repoDef.getDomainType().getProperties()){
String toReplace = property.getName();
createLastWordReplacementCompletion(completions, offset, parseResult, parseResult.lastWord(), toReplace);
}
}

private void createLastWordReplacementCompletion(Collection<ICompletionProposal> completions, int offset, DataRepositoryMethodNameParseResult parseResult, String lastWord, String toReplace) {
if (lastWord == null) {
lastWord = "";
}
if (toReplace.startsWith(lastWord)) {
DocumentEdits edits = new DocumentEdits(null, false);
edits.replace(offset - lastWord.length(), offset, toReplace);
DocumentEdits additionalEdits = new DocumentEdits(null, false);
ICompletionProposal proposal = new FindByCompletionProposal(toReplace, CompletionItemKind.Text, edits, "property " + toReplace, null, Optional.of(additionalEdits), lastWord);
completions.add(proposal);
}
}

private void addMethodCompletionProposal(Collection<ICompletionProposal> completions, int offset, DataRepositoryDefinition repoDef, String localPrefix, String fullPrefix, DataRepositoryMethodNameParseResult parseResult, Map<String, DomainProperty> propertiesByName) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,35 @@ void testPropertyProposals() throws Exception {
}

@Test
void findByComplexExpression() throws Exception {
void testFindByComplexExpression() throws Exception {
checkCompletions("findByResponsibleEmployee", "List<Customer> findByResponsibleEmployee(Employee responsibleEmployee);");
checkCompletions("findByResponsibleEmployee_SocialSecurityNumber", "List<Customer> findByResponsibleEmployee_SocialSecurityNumber(Long responsibleEmployee_SocialSecurityNumber);");
}

@Test
void testAppendKeywords() throws Exception {
checkCompletions("findByFirstName",
"findByFirstNameAnd",
"findByFirstNameExists",
"findByFirstNameIgnoreCase",
"findByFirstNameIsLessThanEqual",
"findByFirstNameOr",
"findByFirstNameOrderBy");
}

@Test
void testAppendKeywordsWithPreviousKeyword() throws Exception {
checkCompletions("findByFirstNameAnd",
"findByFirstNameAndNot");
}

@Test
void testAppendKeywordsStartAlreadyPresent() throws Exception {
checkCompletions("findByFirstNameA",
"findByFirstNameAfter",
"findByFirstNameAnd");
}

private void checkCompletions(String alredyPresent, String... expectedCompletions) throws Exception {
prepareCase("{\n}", "{\n\t" + alredyPresent + "<*>");
assertContainsAnnotationCompletions(Arrays.stream(expectedCompletions).map(expected -> "\t" + expected + "<*>").toArray(String[]::new));
Expand Down