GH-1254: add auto-completion for bean names inside of dependson annotation
This commit is contained in:
@@ -151,17 +151,24 @@ public class Editor {
|
||||
this.harness = harness;
|
||||
this.languageId = LanguageId.of(languageId.getId()); // So we can catch bugs that use == for langauge id comparison.
|
||||
EditorState state = new EditorState(contents);
|
||||
this.doc = harness.openDocument(harness.createWorkingCopy(state.documentContents, this.languageId, extension));
|
||||
|
||||
String tempUri = harness.createTempUri(extension);
|
||||
this.doc = harness.openDocument(harness.createDocFromContentWithResource(state.documentContents, tempUri, this.languageId));
|
||||
|
||||
this.selectionStart = state.selectionStart;
|
||||
this.selectionEnd = state.selectionEnd;
|
||||
this.ignoredTypes = new HashSet<>();
|
||||
this.highlightsFuture = harness.getHighlightsFuture(doc);
|
||||
}
|
||||
|
||||
public Editor(LanguageServerHarness harness, TextDocumentInfo doc, String contents, LanguageId languageId) throws Exception {
|
||||
this.harness = harness;
|
||||
this.languageId = LanguageId.of(languageId.getId()); // So we can catch bugs that use == for langauge id comparison.
|
||||
EditorState state = new EditorState(contents);
|
||||
this.doc = harness.openDocument(doc);
|
||||
|
||||
String tempUri = doc.getUri();
|
||||
this.doc = harness.openDocument(harness.createDocFromContentWithResource(state.documentContents, tempUri, languageId));
|
||||
|
||||
this.selectionStart = state.selectionStart;
|
||||
this.selectionEnd = state.selectionEnd;
|
||||
this.ignoredTypes = new HashSet<>();
|
||||
|
||||
@@ -686,13 +686,13 @@ public class LanguageServerHarness {
|
||||
|
||||
public synchronized Editor newEditor(LanguageId languageId, String contents, String resourceUri) throws Exception {
|
||||
ensureInitialized();
|
||||
TextDocumentInfo doc = docFromResource(contents, resourceUri, languageId);
|
||||
TextDocumentInfo doc = createDocFromContentWithResource(contents, resourceUri, languageId);
|
||||
Editor editor = new Editor(this, doc, contents, languageId);
|
||||
activeEditors.add(editor);
|
||||
return editor;
|
||||
}
|
||||
|
||||
public synchronized TextDocumentInfo docFromResource(String contents, String resourceUri, LanguageId languageId) throws Exception {
|
||||
public synchronized TextDocumentInfo createDocFromContentWithResource(String contents, String resourceUri, LanguageId languageId) throws Exception {
|
||||
TextDocumentItem doc = new TextDocumentItem();
|
||||
doc.setLanguageId(languageId.getId());
|
||||
doc.setText(contents);
|
||||
@@ -703,17 +703,6 @@ public class LanguageServerHarness {
|
||||
return docinfo;
|
||||
}
|
||||
|
||||
public synchronized TextDocumentInfo createWorkingCopy(String contents, LanguageId languageId, String extension) throws Exception {
|
||||
TextDocumentItem doc = new TextDocumentItem();
|
||||
doc.setLanguageId(languageId.getId());
|
||||
doc.setText(contents);
|
||||
doc.setUri(createTempUri(extension));
|
||||
doc.setVersion(getFirstVersion());
|
||||
TextDocumentInfo docinfo = new TextDocumentInfo(doc);
|
||||
documents.put(docinfo.getUri(), docinfo);
|
||||
return docinfo;
|
||||
}
|
||||
|
||||
protected int getFirstVersion() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2020, 2023 Pivotal, Inc.
|
||||
* Copyright (c) 2020, 2024 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
|
||||
@@ -21,8 +21,10 @@ import org.eclipse.lsp4j.CompletionItemKind;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
|
||||
import org.springframework.ide.vscode.boot.java.Annotations;
|
||||
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies;
|
||||
import org.springframework.ide.vscode.boot.java.beans.DependsOnCompletionProcessor;
|
||||
import org.springframework.ide.vscode.boot.java.data.DataRepositoryCompletionProcessor;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.BootJavaCompletionEngine;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.CompletionProvider;
|
||||
@@ -100,7 +102,8 @@ public class BootJavaCompletionEngineConfigurer {
|
||||
BootLanguageServerParams params,
|
||||
@Qualifier("adHocProperties") ProjectBasedPropertyIndexProvider adHocProperties,
|
||||
JavaSnippetManager snippetManager,
|
||||
CompilationUnitCache cuCache) {
|
||||
CompilationUnitCache cuCache,
|
||||
SpringMetamodelIndex springIndex) {
|
||||
|
||||
SpringPropertyIndexProvider indexProvider = params.indexProvider;
|
||||
JavaProjectFinder javaProjectFinder = params.projectFinder;
|
||||
@@ -109,6 +112,7 @@ public class BootJavaCompletionEngineConfigurer {
|
||||
|
||||
providers.put(Annotations.SCOPE, new ScopeCompletionProcessor());
|
||||
providers.put(Annotations.VALUE, new ValueCompletionProcessor(javaProjectFinder, indexProvider, adHocProperties));
|
||||
providers.put(Annotations.DEPENDS_ON, new DependsOnCompletionProcessor(javaProjectFinder, springIndex));
|
||||
providers.put(Annotations.REPOSITORY, new DataRepositoryCompletionProcessor());
|
||||
|
||||
return new BootJavaCompletionEngine(cuCache, providers, snippetManager);
|
||||
|
||||
@@ -0,0 +1,275 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2024 Broadcom
|
||||
* 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:
|
||||
* Broadcom - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.beans;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
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.ITypeBinding;
|
||||
import org.eclipse.jdt.core.dom.MemberValuePair;
|
||||
import org.eclipse.jdt.core.dom.SimpleName;
|
||||
import org.eclipse.jdt.core.dom.StringLiteral;
|
||||
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.CompletionProvider;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
|
||||
import org.springframework.ide.vscode.commons.util.BadLocationException;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class DependsOnCompletionProcessor implements CompletionProvider {
|
||||
|
||||
private final JavaProjectFinder projectFinder;
|
||||
private final SpringMetamodelIndex springIndex;
|
||||
|
||||
public DependsOnCompletionProcessor(JavaProjectFinder projectFinder, SpringMetamodelIndex springIndex) {
|
||||
this.projectFinder = projectFinder;
|
||||
this.springIndex = springIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void provideCompletions(ASTNode node, Annotation annotation, ITypeBinding type, int offset, TextDocument doc, Collection<ICompletionProposal> completions) {
|
||||
|
||||
Optional<IJavaProject> optionalProject = projectFinder.find(doc.getId());
|
||||
if (!optionalProject.isPresent()) {
|
||||
return;
|
||||
}
|
||||
|
||||
IJavaProject project = optionalProject.get();
|
||||
|
||||
try {
|
||||
|
||||
// case: @DependsOn(<*>)
|
||||
if (node == annotation && doc.get(offset - 1, 2).endsWith("()")) {
|
||||
Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName());
|
||||
|
||||
for (Bean bean : beans) {
|
||||
|
||||
DocumentEdits edits = new DocumentEdits(doc, false);
|
||||
edits.replace(offset, offset, "\"" + bean.getName() + "\"");
|
||||
|
||||
// PT-160455522: create a proposal with `PlainText` format type, because for vscode (but not Eclipse), if you send it as a snippet
|
||||
// and it is "place holder" as such `"${debug}"`, vscode may treat it as a snippet place holder, and insert an empty string
|
||||
// if it cannot resolve it. If sending this as plain text, then insertion happens correctly
|
||||
DependsOnCompletionProposal proposal = new DependsOnCompletionProposal(edits, bean.getName(), bean.getName(), null);
|
||||
|
||||
completions.add(proposal);
|
||||
}
|
||||
}
|
||||
// case: @DependsOn(prefix<*>)
|
||||
else if (node instanceof SimpleName && node.getParent() instanceof Annotation) {
|
||||
computeProposalsForSimpleName(project, node, completions, offset, doc);
|
||||
}
|
||||
// case: @DependsOn(value=<*>)
|
||||
else if (node instanceof SimpleName && node.getParent() instanceof MemberValuePair
|
||||
&& "value".equals(((MemberValuePair)node.getParent()).getName().toString())) {
|
||||
computeProposalsForSimpleName(project, node, completions, offset, doc);
|
||||
}
|
||||
// case: @DependsOn("prefix<*>")
|
||||
else if (node instanceof StringLiteral && node.getParent() instanceof Annotation) {
|
||||
if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) {
|
||||
computeProposalsForStringLiteral(project, node, completions, offset, doc);
|
||||
}
|
||||
}
|
||||
else if (node instanceof StringLiteral && node.getParent() instanceof ArrayInitializer) {
|
||||
if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) {
|
||||
computeProposalsForInsideArrayInitializer(project, node, completions, offset, doc);
|
||||
}
|
||||
}
|
||||
// case: @DependsOn(value="prefix<*>")
|
||||
else if (node instanceof StringLiteral && node.getParent() instanceof MemberValuePair
|
||||
&& "value".equals(((MemberValuePair)node.getParent()).getName().toString())) {
|
||||
if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) {
|
||||
computeProposalsForStringLiteral(project, node, completions, offset, doc);
|
||||
}
|
||||
}
|
||||
// case: @DependsOn({<*>})
|
||||
else if (node instanceof ArrayInitializer && node.getParent() instanceof Annotation) {
|
||||
computeProposalsForArrayInitializr(project, (ArrayInitializer) node, completions, offset, doc);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void computeProposalsForSimpleName(IJavaProject project, ASTNode node, Collection<ICompletionProposal> completions, int offset, IDocument doc) {
|
||||
String prefix = identifyPropertyPrefix(node.toString(), offset - node.getStartPosition());
|
||||
|
||||
int startOffset = node.getStartPosition();
|
||||
int endOffset = node.getStartPosition() + node.getLength();
|
||||
|
||||
String proposalPrefix = "\"";
|
||||
String proposalPostfix = "\"";
|
||||
|
||||
Set<String> mentionedBeans = alreadyMentionedBeans(node);
|
||||
|
||||
Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName());
|
||||
List<Bean> matchingBeans = Arrays.stream(beans)
|
||||
.filter(bean -> bean.getName().toLowerCase().startsWith(prefix.toLowerCase()))
|
||||
.filter(bean -> !mentionedBeans.contains(bean.getName()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
for (Bean bean : matchingBeans) {
|
||||
|
||||
DocumentEdits edits = new DocumentEdits(doc, false);
|
||||
edits.replace(startOffset, endOffset, proposalPrefix + bean.getName() + proposalPostfix);
|
||||
|
||||
// PT-160455522: create a proposal with `PlainText` format type, because for vscode (but not Eclipse), if you send it as a snippet
|
||||
// and it is "place holder" as such `"${debug}"`, vscode may treat it as a snippet place holder, and insert an empty string
|
||||
// if it cannot resolve it. If sending this as plain text, then insertion happens correctly
|
||||
DependsOnCompletionProposal proposal = new DependsOnCompletionProposal(edits, bean.getName(), bean.getName(), null);
|
||||
|
||||
completions.add(proposal);
|
||||
}
|
||||
}
|
||||
|
||||
private void computeProposalsForStringLiteral(IJavaProject project, ASTNode node, Collection<ICompletionProposal> completions, int offset, IDocument doc) throws BadLocationException {
|
||||
int length = offset - (node.getStartPosition() + 1);
|
||||
|
||||
String prefix = identifyPropertyPrefix(doc.get(node.getStartPosition() + 1, length), length);
|
||||
int startOffset = offset - prefix.length();
|
||||
int endOffset = offset;
|
||||
|
||||
Set<String> mentionedBeans = alreadyMentionedBeans(node);
|
||||
|
||||
Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName());
|
||||
|
||||
final String filterPrefix = prefix;
|
||||
List<Bean> matchingBeans = Arrays.stream(beans)
|
||||
.filter(bean -> bean.getName().toLowerCase().startsWith(filterPrefix.toLowerCase()))
|
||||
.filter(bean -> !mentionedBeans.contains(bean.getName()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
for (Bean bean : matchingBeans) {
|
||||
|
||||
DocumentEdits edits = new DocumentEdits(doc, false);
|
||||
edits.replace(startOffset, endOffset, bean.getName());
|
||||
|
||||
// PT-160455522: create a proposal with `PlainText` format type, because for vscode (but not Eclipse), if you send it as a snippet
|
||||
// and it is "place holder" as such `"${debug}"`, vscode may treat it as a snippet place holder, and insert an empty string
|
||||
// if it cannot resolve it. If sending this as plain text, then insertion happens correctly
|
||||
DependsOnCompletionProposal proposal = new DependsOnCompletionProposal(edits, bean.getName(), bean.getName(), null);
|
||||
|
||||
completions.add(proposal);
|
||||
}
|
||||
}
|
||||
|
||||
private void computeProposalsForArrayInitializr(IJavaProject project, ArrayInitializer node, Collection<ICompletionProposal> completions, int offset, IDocument doc) {
|
||||
Set<String> mentionedBeans = alreadyMentionedBeans(node);
|
||||
|
||||
Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName());
|
||||
List<Bean> filteredBeans = Arrays.stream(beans)
|
||||
.filter(bean -> !mentionedBeans.contains(bean.getName()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
for (Bean bean : filteredBeans) {
|
||||
|
||||
DocumentEdits edits = new DocumentEdits(doc, false);
|
||||
edits.replace(offset, offset, "\"" + bean.getName() + "\"");
|
||||
|
||||
// PT-160455522: create a proposal with `PlainText` format type, because for vscode (but not Eclipse), if you send it as a snippet
|
||||
// and it is "place holder" as such `"${debug}"`, vscode may treat it as a snippet place holder, and insert an empty string
|
||||
// if it cannot resolve it. If sending this as plain text, then insertion happens correctly
|
||||
DependsOnCompletionProposal proposal = new DependsOnCompletionProposal(edits, bean.getName(), bean.getName(), null);
|
||||
|
||||
completions.add(proposal);
|
||||
}
|
||||
}
|
||||
|
||||
private void computeProposalsForInsideArrayInitializer(IJavaProject project, ASTNode node, Collection<ICompletionProposal> completions, int offset, TextDocument doc) throws BadLocationException {
|
||||
int length = offset - (node.getStartPosition() + 1);
|
||||
if (length >= 0) {
|
||||
computeProposalsForStringLiteral(project, node, completions, offset, doc);
|
||||
}
|
||||
else {
|
||||
Set<String> mentionedBeans = alreadyMentionedBeans(node);
|
||||
|
||||
Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName());
|
||||
List<Bean> filteredBeans = Arrays.stream(beans)
|
||||
.filter(bean -> !mentionedBeans.contains(bean.getName()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
for (Bean bean : filteredBeans) {
|
||||
|
||||
DocumentEdits edits = new DocumentEdits(doc, false);
|
||||
edits.replace(offset, offset, "\"" + bean.getName() + "\",");
|
||||
|
||||
// PT-160455522: create a proposal with `PlainText` format type, because for vscode (but not Eclipse), if you send it as a snippet
|
||||
// and it is "place holder" as such `"${debug}"`, vscode may treat it as a snippet place holder, and insert an empty string
|
||||
// if it cannot resolve it. If sending this as plain text, then insertion happens correctly
|
||||
DependsOnCompletionProposal proposal = new DependsOnCompletionProposal(edits, bean.getName(), bean.getName(), null);
|
||||
|
||||
completions.add(proposal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String identifyPropertyPrefix(String nodeContent, int offset) {
|
||||
String result = nodeContent.substring(0, offset);
|
||||
|
||||
int i = offset - 1;
|
||||
while (i >= 0) {
|
||||
char c = nodeContent.charAt(i);
|
||||
if (c == '}' || c == '{' || c == '$' || c == '#') {
|
||||
result = result.substring(i + 1, offset);
|
||||
break;
|
||||
}
|
||||
i--;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private Set<String> alreadyMentionedBeans(ASTNode node) {
|
||||
Set<String> result = new HashSet<>();
|
||||
|
||||
ArrayInitializer arrayNode = null;
|
||||
while (node != null && arrayNode == null && !(node instanceof Annotation)) {
|
||||
if (node instanceof ArrayInitializer) {
|
||||
arrayNode = (ArrayInitializer) node;
|
||||
}
|
||||
else {
|
||||
node = node.getParent();
|
||||
}
|
||||
}
|
||||
|
||||
if (arrayNode != null) {
|
||||
List<?> expressions = arrayNode.expressions();
|
||||
for (Object expression : expressions) {
|
||||
if (expression instanceof StringLiteral) {
|
||||
StringLiteral stringExr = (StringLiteral) expression;
|
||||
String value = stringExr.getLiteralValue();
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2024 Broadcom
|
||||
* 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:
|
||||
* Broadcom - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.beans;
|
||||
|
||||
import org.eclipse.lsp4j.CompletionItemKind;
|
||||
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.Renderable;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class DependsOnCompletionProposal implements ICompletionProposal {
|
||||
|
||||
private static final String EMPTY_DETAIL = "";
|
||||
|
||||
private DocumentEdits edits;
|
||||
private String label;
|
||||
private String detail;
|
||||
private Renderable documentation;
|
||||
|
||||
public DependsOnCompletionProposal(DocumentEdits edits, String label, String detail, Renderable documentation) {
|
||||
this.edits = edits;
|
||||
this.label = label;
|
||||
// PT 161489998 - Detail for proposal must not be null. For some clients like Eclipse,
|
||||
// a null detail results in an NPE at JDT level when inserting the proposal in the editor, and results
|
||||
// in odd behaviour like insertion of an extra new line.
|
||||
this.detail = detail == null ? EMPTY_DETAIL : detail;
|
||||
this.documentation = documentation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLabel() {
|
||||
return this.label;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionItemKind getKind() {
|
||||
return CompletionItemKind.Value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DocumentEdits getTextEdit() {
|
||||
return this.edits;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDetail() {
|
||||
return this.detail;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Renderable getDocumentation() {
|
||||
return this.documentation;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2024 Broadcom
|
||||
* 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:
|
||||
* Broadcom - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.beans.test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.lsp4j.CompletionItem;
|
||||
import org.eclipse.lsp4j.Location;
|
||||
import org.eclipse.lsp4j.Position;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.ide.vscode.boot.app.SpringSymbolIndex;
|
||||
import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest;
|
||||
import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf;
|
||||
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
|
||||
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.junit.jupiter.SpringExtension;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@BootLanguageServerTest
|
||||
@Import(SymbolProviderTestConf.class)
|
||||
public class DependsOnCompletionProviderTest {
|
||||
|
||||
@Autowired private BootLanguageServerHarness harness;
|
||||
@Autowired private JavaProjectFinder projectFinder;
|
||||
@Autowired private SpringMetamodelIndex springIndex;
|
||||
@Autowired private SpringSymbolIndex indexer;
|
||||
|
||||
private File directory;
|
||||
private IJavaProject project;
|
||||
private Bean[] indexedBeans;
|
||||
private String tempJavaDocUri;
|
||||
private Bean bean1;
|
||||
private Bean bean2;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() throws Exception {
|
||||
harness.intialize(null);
|
||||
|
||||
directory = new File(ProjectsHarness.class.getResource("/test-projects/test-spring-indexing/").toURI());
|
||||
|
||||
String projectDir = directory.toURI().toString();
|
||||
project = projectFinder.find(new TextDocumentIdentifier(projectDir)).get();
|
||||
|
||||
CompletableFuture<Void> initProject = indexer.waitOperation();
|
||||
initProject.get(5, TimeUnit.SECONDS);
|
||||
|
||||
indexedBeans = springIndex.getBeansOfProject(project.getElementName());
|
||||
|
||||
tempJavaDocUri = directory.toPath().resolve("src/main/java/org/test/TempClass.java").toUri().toString();
|
||||
bean1 = new Bean("bean1", "type1", new Location(tempJavaDocUri, new Range(new Position(1,1), new Position(1, 20))), null, null, null);
|
||||
bean2 = new Bean("bean2", "type2", new Location(tempJavaDocUri, new Range(new Position(1,1), new Position(1, 20))), null, null, null);
|
||||
|
||||
springIndex.updateBeans(project.getElementName(), new Bean[] {bean1, bean2});
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void restoreIndexState() {
|
||||
this.springIndex.updateBeans(project.getElementName(), indexedBeans);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDependsOnCompletionWithoutQuotesWithoutPrefix() throws Exception {
|
||||
assertCompletions("@DependsOn(<*>)", 2, "@DependsOn(\"bean1\"<*>)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDependsOnCompletionWithoutQuotesWithPrefix() throws Exception {
|
||||
assertCompletions("@DependsOn(be<*>)", 2, "@DependsOn(\"bean1\"<*>)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDependsOnCompletionWithoutQuotesWithAttributeName() throws Exception {
|
||||
assertCompletions("@DependsOn(value=<*>)", 2, "@DependsOn(value=\"bean1\"<*>)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDependsOnCompletionInsideOfQuotesWithoutPrefix() throws Exception {
|
||||
assertCompletions("@DependsOn(\"<*>\")", 2, "@DependsOn(\"bean1<*>\")");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDependsOnCompletionWithoutQuotesWithoutPrefixInsideArray() throws Exception {
|
||||
assertCompletions("@DependsOn({<*>})", 2, "@DependsOn({\"bean1\"<*>})");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDependsOnCompletionInsideOfQuotesWithoutPrefixInsideArray() throws Exception {
|
||||
assertCompletions("@DependsOn({\"<*>\"})", 2, "@DependsOn({\"bean1<*>\"})");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDependsOnCompletionInsideOfQuotesWithPrefix() throws Exception {
|
||||
assertCompletions("@DependsOn(\"be<*>\")", 2, "@DependsOn(\"bean1<*>\")");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDependsOnCompletionInsideOfQuotesAndArrayWithPrefix() throws Exception {
|
||||
assertCompletions("@DependsOn({\"be<*>\"})", 2, "@DependsOn({\"bean1<*>\"})");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDependsOnCompletionInsideOfQuotesWithPrefixButWithoutMatches() throws Exception {
|
||||
assertCompletions("@DependsOn(\"XXX<*>\")", 0, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDependsOnCompletionOutsideOfAnnotation1() throws Exception {
|
||||
assertCompletions("@DependsOn(\"XXX\")<*>", 0, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDependsOnCompletionOutsideOfAnnotation2() throws Exception {
|
||||
assertCompletions("@DependsOn<*>(\"XXX\")", 0, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDependsOnCompletionInsideOfQuotesWithPrefixAndReplacedPostfix() throws Exception {
|
||||
assertCompletions("@DependsOn(\"be<*>xxx\")", 2, "@DependsOn(\"bean1<*>xxx\")");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDependsOnCompletionInsideOfArrayBehindExistingElement() throws Exception {
|
||||
assertCompletions("@DependsOn({\"bean1\",<*>})", 1, "@DependsOn({\"bean1\",\"bean2\"<*>})");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDependsOnCompletionInsideOfArrayInFrontOfExistingElement() throws Exception {
|
||||
assertCompletions("@DependsOn({<*>\"bean1\"})", 1, "@DependsOn({\"bean2\",<*>\"bean1\"})");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDependsOnCompletionInsideOfArrayBetweenExistingElements() throws Exception {
|
||||
Bean bean3 = new Bean("bean3", "type3", new Location(tempJavaDocUri, new Range(new Position(1,1), new Position(1, 20))), null, null, null);
|
||||
springIndex.updateBeans(project.getElementName(), new Bean[] {bean1, bean2, bean3});
|
||||
|
||||
assertCompletions("@DependsOn({\"bean1\",<*>\"bean2\"})", 1, "@DependsOn({\"bean1\",\"bean3\",<*>\"bean2\"})");
|
||||
}
|
||||
|
||||
private void assertCompletions(String completionLine, int noOfExcpectedCompletions, String expectedCompletedLine) throws Exception {
|
||||
String editorContent = """
|
||||
package org.test;
|
||||
|
||||
import org.springframework.context.annotation.DependsOn;
|
||||
|
||||
@Component
|
||||
""" +
|
||||
completionLine + "\n" +
|
||||
"""
|
||||
public class TestDependsOnClass {
|
||||
}
|
||||
""";
|
||||
|
||||
Editor editor = harness.newEditor(LanguageId.JAVA, editorContent, tempJavaDocUri);
|
||||
|
||||
List<CompletionItem> completions = editor.getCompletions();
|
||||
assertEquals(noOfExcpectedCompletions, completions.size());
|
||||
|
||||
if (noOfExcpectedCompletions > 0) {
|
||||
editor.apply(completions.get(0));
|
||||
assertEquals("""
|
||||
package org.test;
|
||||
|
||||
import org.springframework.context.annotation.DependsOn;
|
||||
|
||||
@Component
|
||||
""" + expectedCompletedLine + "\n" +
|
||||
"""
|
||||
public class TestDependsOnClass {
|
||||
}
|
||||
""", editor.getText());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user