Skip to content

Commit 2e79692

Browse files
danthe1stBoykoAlex
authored andcommitted
completions for predicate keywords in Spring Data repositories
1 parent a68aa38 commit 2e79692

File tree

4 files changed

+72
-17
lines changed

4 files changed

+72
-17
lines changed

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/providers/prefixsensitive/DataRepositoryMethodNameParseResult.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ record DataRepositoryMethodNameParseResult(
3333
*/
3434
boolean performFullCompletion,
3535
/**
36-
* the last entered word, which completion options should be used for completing the expression.
36+
* the last entered word (if it is not a keyword), which completion options should be used for completing the expression.
3737
*
38-
* e.g. {@code First} in {@code findByFirst} which could be completed to {@code findByFirstName}
38+
* 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
3939
*/
4040
String lastWord,
4141
/**

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/providers/prefixsensitive/DataRepositoryMethodParser.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,7 @@ class DataRepositoryMethodParser {
3030

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

3735
private final String prefix;
3836
private final Map<String, List<DomainProperty>> propertiesGroupedByFirstWord;

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/providers/prefixsensitive/DataRepositoryPrefixSensitiveCompletionProvider.java

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -47,23 +47,56 @@ public void addProposals(Collection<ICompletionProposal> completions, IDocument
4747
if (parseResult.lastWord() == null || !propertiesByName.containsKey(parseResult.lastWord())) {
4848
addPropertyProposals(completions, offset, repoDef, parseResult);
4949
}
50+
addPredicateKeywordProposals(completions, offset, prefix, parseResult, propertiesByName);
5051
}
5152
}
5253

53-
private void addPropertyProposals(Collection<ICompletionProposal> completions, int offset, DataRepositoryDefinition repoDef, DataRepositoryMethodNameParseResult parseResult) {
54-
for(DomainProperty property : repoDef.getDomainType().getProperties()){
55-
String lastWord = parseResult.lastWord();
56-
if (lastWord == null) {
57-
lastWord = "";
54+
private void addPredicateKeywordProposals(Collection<ICompletionProposal> completions, int offset, String prefix, DataRepositoryMethodNameParseResult parseResult, Map<String, DomainProperty> propertiesByName) {
55+
String lastWord = findLastWordWithoutPrefixingProperty(parseResult, propertiesByName);
56+
for (QueryPredicateKeywordInfo predicate : QueryPredicateKeywordInfo.PREDICATE_KEYWORDS){
57+
if (parseResult.allowedKeywordTypes().contains(predicate.type())) {
58+
createLastWordReplacementCompletion(completions, offset, parseResult, lastWord, predicate.keyword());
5859
}
59-
if (property.getName().startsWith(lastWord)) {
60-
DocumentEdits edits = new DocumentEdits(null, false);
61-
edits.replace(offset - lastWord.length(), offset, property.getName());
62-
DocumentEdits additionalEdits = new DocumentEdits(null, false);
63-
ICompletionProposal proposal = new FindByCompletionProposal(property.getName(), CompletionItemKind.Text, edits, "property " + property.getName(), null, Optional.of(additionalEdits), lastWord);
64-
completions.add(proposal);
60+
}
61+
}
62+
63+
private String findLastWordWithoutPrefixingProperty(DataRepositoryMethodNameParseResult parseResult, Map<String, DomainProperty> propertiesByName) {
64+
String lastWord = parseResult.lastWord();
65+
if (lastWord == null) {
66+
return "";
67+
}
68+
if (propertiesByName.containsKey(lastWord)) {
69+
return "";
70+
}
71+
for (int i = lastWord.length() - 1; i >= 0; i--) {
72+
if (Character.isUpperCase(lastWord.charAt(i))) {
73+
String substring = lastWord.substring(0,i);
74+
if (propertiesByName.containsKey(substring)) {
75+
return lastWord.substring(i);
76+
}
6577
}
6678
}
79+
return lastWord;
80+
}
81+
82+
private void addPropertyProposals(Collection<ICompletionProposal> completions, int offset, DataRepositoryDefinition repoDef, DataRepositoryMethodNameParseResult parseResult) {
83+
for(DomainProperty property : repoDef.getDomainType().getProperties()){
84+
String toReplace = property.getName();
85+
createLastWordReplacementCompletion(completions, offset, parseResult, parseResult.lastWord(), toReplace);
86+
}
87+
}
88+
89+
private void createLastWordReplacementCompletion(Collection<ICompletionProposal> completions, int offset, DataRepositoryMethodNameParseResult parseResult, String lastWord, String toReplace) {
90+
if (lastWord == null) {
91+
lastWord = "";
92+
}
93+
if (toReplace.startsWith(lastWord)) {
94+
DocumentEdits edits = new DocumentEdits(null, false);
95+
edits.replace(offset - lastWord.length(), offset, toReplace);
96+
DocumentEdits additionalEdits = new DocumentEdits(null, false);
97+
ICompletionProposal proposal = new FindByCompletionProposal(toReplace, CompletionItemKind.Text, edits, "property " + toReplace, null, Optional.of(additionalEdits), lastWord);
98+
completions.add(proposal);
99+
}
67100
}
68101

69102
private void addMethodCompletionProposal(Collection<ICompletionProposal> completions, int offset, DataRepositoryDefinition repoDef, String localPrefix, String fullPrefix, DataRepositoryMethodNameParseResult parseResult, Map<String, DomainProperty> propertiesByName) {

headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryCompletionProcessorTest.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,11 +138,35 @@ void testPropertyProposals() throws Exception {
138138
}
139139

140140
@Test
141-
void findByComplexExpression() throws Exception {
141+
void testFindByComplexExpression() throws Exception {
142142
checkCompletions("findByResponsibleEmployee", "List<Customer> findByResponsibleEmployee(Employee responsibleEmployee);");
143143
checkCompletions("findByResponsibleEmployee_SocialSecurityNumber", "List<Customer> findByResponsibleEmployee_SocialSecurityNumber(Long responsibleEmployee_SocialSecurityNumber);");
144144
}
145145

146+
@Test
147+
void testAppendKeywords() throws Exception {
148+
checkCompletions("findByFirstName",
149+
"findByFirstNameAnd",
150+
"findByFirstNameExists",
151+
"findByFirstNameIgnoreCase",
152+
"findByFirstNameIsLessThanEqual",
153+
"findByFirstNameOr",
154+
"findByFirstNameOrderBy");
155+
}
156+
157+
@Test
158+
void testAppendKeywordsWithPreviousKeyword() throws Exception {
159+
checkCompletions("findByFirstNameAnd",
160+
"findByFirstNameAndNot");
161+
}
162+
163+
@Test
164+
void testAppendKeywordsStartAlreadyPresent() throws Exception {
165+
checkCompletions("findByFirstNameA",
166+
"findByFirstNameAfter",
167+
"findByFirstNameAnd");
168+
}
169+
146170
private void checkCompletions(String alredyPresent, String... expectedCompletions) throws Exception {
147171
prepareCase("{\n}", "{\n\t" + alredyPresent + "<*>");
148172
assertContainsAnnotationCompletions(Arrays.stream(expectedCompletions).map(expected -> "\t" + expected + "<*>").toArray(String[]::new));

0 commit comments

Comments
 (0)