diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootJavaCompletionEngineConfigurer.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootJavaCompletionEngineConfigurer.java index 4fcb784e67..14e46ebbe2 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootJavaCompletionEngineConfigurer.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootJavaCompletionEngineConfigurer.java @@ -40,6 +40,7 @@ import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache; import org.springframework.ide.vscode.boot.java.value.ValueCompletionProcessor; import org.springframework.ide.vscode.boot.java.contextconfiguration.ContextConfigurationProcessor; +import org.springframework.ide.vscode.boot.java.conditionalonresource.ConditionalOnResourceProcessor; import org.springframework.ide.vscode.boot.metadata.ProjectBasedPropertyIndexProvider; import org.springframework.ide.vscode.boot.metadata.SpringPropertyIndexProvider; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; @@ -117,6 +118,7 @@ BootJavaCompletionEngine javaCompletionEngine( providers.put(Annotations.VALUE, new ValueCompletionProcessor(javaProjectFinder, indexProvider, adHocProperties)); providers.put(Annotations.CONTEXT_CONFIGURATION, new ContextConfigurationProcessor(javaProjectFinder)); + providers.put(Annotations.CONDITIONAL_ON_RESOURCE, new ConditionalOnResourceProcessor(javaProjectFinder)); providers.put(Annotations.REPOSITORY, new DataRepositoryCompletionProcessor()); providers.put(Annotations.SCOPE, new AnnotationAttributeCompletionProcessor(javaProjectFinder, Map.of("value", new ScopeCompletionProcessor()))); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerBootApp.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerBootApp.java index 8bd40d6e28..e3702f54d0 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerBootApp.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerBootApp.java @@ -71,6 +71,7 @@ import org.springframework.ide.vscode.boot.java.reconcilers.JdtReconciler; import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache; import org.springframework.ide.vscode.boot.java.value.ValueDefinitionProvider; +import org.springframework.ide.vscode.boot.java.conditionalonresource.ConditionalOnResourceDefinitionProvider; import org.springframework.ide.vscode.boot.jdt.ls.JavaProjectsService; import org.springframework.ide.vscode.boot.jdt.ls.JdtLsProjectCache; import org.springframework.ide.vscode.boot.metadata.AdHocSpringPropertyIndexProvider; @@ -399,6 +400,7 @@ BootJavaCodeActionProvider getBootJavaCodeActionProvider(JavaProjectFinder proje JavaDefinitionHandler javaDefinitionHandler(SimpleLanguageServer server, CompilationUnitCache cuCache, JavaProjectFinder projectFinder, SpringMetamodelIndex springIndex, JdtDataQuerySemanticTokensProvider qurySemanticTokens) { return new JavaDefinitionHandler(cuCache, projectFinder, List.of( new ValueDefinitionProvider(), + new ConditionalOnResourceDefinitionProvider(), new DependsOnDefinitionProvider(springIndex), new ResourceDefinitionProvider(springIndex), new QualifierDefinitionProvider(springIndex), diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/conditionalonresource/ConditionalOnResourceDefinitionProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/conditionalonresource/ConditionalOnResourceDefinitionProvider.java new file mode 100644 index 0000000000..3cae2bda6a --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/conditionalonresource/ConditionalOnResourceDefinitionProvider.java @@ -0,0 +1,108 @@ +/******************************************************************************* + * Copyright (c) 2024 Broadcom, 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: + * Broadcom, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.conditionalonresource; + +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.Annotation; +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.Expression; +import org.eclipse.jdt.core.dom.IAnnotationBinding; +import org.eclipse.jdt.core.dom.MemberValuePair; +import org.eclipse.jdt.core.dom.NormalAnnotation; +import org.eclipse.jdt.core.dom.SingleMemberAnnotation; +import org.eclipse.jdt.core.dom.StringLiteral; +import org.eclipse.lsp4j.Location; +import org.eclipse.lsp4j.LocationLink; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.Range; +import org.eclipse.lsp4j.TextDocumentIdentifier; +import org.eclipse.lsp4j.jsonrpc.CancelChecker; +import org.eclipse.lsp4j.jsonrpc.CancelChecker; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ide.vscode.boot.java.Annotations; +import org.springframework.ide.vscode.boot.java.IJavaDefinitionProvider; +import org.springframework.ide.vscode.boot.properties.BootPropertiesLanguageServerComponents; +import org.springframework.ide.vscode.commons.java.IClasspathUtil; +import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.yaml.snakeyaml.nodes.Node; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableList.Builder; + +public class ConditionalOnResourceDefinitionProvider implements IJavaDefinitionProvider { + + private static final Logger log = LoggerFactory.getLogger(ConditionalOnResourceDefinitionProvider.class); + + @Override + public List getDefinitions(CancelChecker cancelToken, IJavaProject project, + TextDocumentIdentifier docId, CompilationUnit cu, ASTNode n, int offset) { + + if (n instanceof StringLiteral) { + StringLiteral valueNode = (StringLiteral) n; + + String literalValue = valueNode.getLiteralValue(); + if (literalValue != null) { + if (literalValue.startsWith("classpath")) { + return getDefinitionForClasspathResource(project, cu, valueNode, literalValue); + } + } + } + return Collections.emptyList(); + } + + + private List getDefinitionForClasspathResource(IJavaProject project, CompilationUnit cu, StringLiteral valueNode, String literalValue) { + literalValue = literalValue.substring("classpath:".length()); + + String[] resources = findResources(project, literalValue); + + List result = new ArrayList<>(); + + for (String resource : resources) { + String uri = "file://" + resource; + + Position startPosition = new Position(cu.getLineNumber(valueNode.getStartPosition()) - 1, + cu.getColumnNumber(valueNode.getStartPosition())); + Position endPosition = new Position( + cu.getLineNumber(valueNode.getStartPosition() + valueNode.getLength()) - 1, + cu.getColumnNumber(valueNode.getStartPosition() + valueNode.getLength())); + Range nodeRange = new Range(startPosition, endPosition); + + LocationLink locationLink = new LocationLink(uri, + new Range(new Position(0, 0), new Position(0, 0)), new Range(new Position(0, 0), new Position(0, 0)), + nodeRange); + + result.add(locationLink); + } + + return result; + } + + private String[] findResources(IJavaProject project, String resource) { + String[] resources = IClasspathUtil.getClasspathResourcesFullPaths(project.getClasspath()) + .filter(path -> path.toString().endsWith(resource)) + .map(path -> path.toString()) + .toArray(String[]::new); + + return resources; + } + +} \ No newline at end of file diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/conditionalonresource/ConditionalOnResourceProcessor.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/conditionalonresource/ConditionalOnResourceProcessor.java new file mode 100644 index 0000000000..c777705adf --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/conditionalonresource/ConditionalOnResourceProcessor.java @@ -0,0 +1,167 @@ +/******************************************************************************* + * Copyright (c) 2024 Broadcom, 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: + * Broadcom, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.conditionalonresource; + +import static org.springframework.ide.vscode.commons.util.StringUtil.camelCaseToHyphens; + +import java.nio.file.Paths; +import java.util.*; +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.ITypeBinding; +import org.eclipse.jdt.core.dom.MemberValuePair; +import org.eclipse.jdt.core.dom.QualifiedName; +import org.eclipse.jdt.core.dom.SimpleName; +import org.eclipse.jdt.core.dom.StringLiteral; +import org.eclipse.lsp4j.TextDocumentIdentifier; +import org.openrewrite.yaml.internal.grammar.JsonPathParser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ide.vscode.boot.java.annotations.AnnotationAttributeCompletionProposal; +import org.springframework.ide.vscode.boot.java.handlers.CompletionProvider; +import org.springframework.ide.vscode.boot.metadata.ProjectBasedPropertyIndexProvider; +import org.springframework.ide.vscode.boot.metadata.PropertyInfo; +import org.springframework.ide.vscode.boot.metadata.SpringPropertyIndexProvider; +import org.springframework.ide.vscode.commons.java.IClasspathUtil; +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.util.BadLocationException; +import org.springframework.ide.vscode.commons.util.FuzzyMap; +import org.springframework.ide.vscode.commons.util.FuzzyMap.Match; +import org.springframework.ide.vscode.commons.util.text.IDocument; +import org.springframework.ide.vscode.commons.util.text.TextDocument; + +/** + * @author Karthik Sankaranarayanan + */ +public class ConditionalOnResourceProcessor implements CompletionProvider { + + private static final Logger log = LoggerFactory.getLogger(ConditionalOnResourceProcessor.class); + + private final JavaProjectFinder projectFinder; + + public ConditionalOnResourceProcessor(JavaProjectFinder projectFinder) { + this.projectFinder = projectFinder; + } + + @Override + public void provideCompletions(ASTNode node, Annotation annotation, ITypeBinding type, + int offset, TextDocument doc, Collection completions) { + + try { + Optional optionalProject = this.projectFinder.find(doc.getId()); + if (optionalProject.isEmpty()) { + return; + } + + IJavaProject project = optionalProject.get(); + + // case: @ConditionalOnResource(resources=<*>) + if (node instanceof SimpleName && node.getParent() instanceof MemberValuePair + && ("resources".equals(((MemberValuePair)node.getParent()).getName().toString()))) { + computeProposalsForSimpleName(project, node, completions, offset, doc); + } + // case: @ConditionalOnResource(resources=<*>) + else if (node instanceof SimpleName && node.getParent() instanceof QualifiedName && node.getParent().getParent() instanceof MemberValuePair + && ("resources".equals(((MemberValuePair)node.getParent().getParent()).getName().toString()))) { + computeProposalsForSimpleName(project, node.getParent(), completions, offset, doc); + } + // case:@ConditionalOnResource(resources="prefix<*>") + else if (node instanceof StringLiteral && node.getParent() instanceof MemberValuePair + && ("resources".equals(((MemberValuePair)node.getParent()).getName().toString()))) { + if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) { + computeProposalsForStringLiteral(project, (StringLiteral) node, completions, offset, doc); + } + } + } + catch (Exception e) { + log.error("problem while looking for ConditionalOnResource annotation proposals", e); + } + } + + private void addClasspathResourceProposals(IJavaProject project, TextDocument doc, int startOffset, int endOffset, String prefix, boolean includeQuotes, Collection completions) { + String[] resources = findResources(project, prefix); + + double score = resources.length + 1000; + for (String resource : resources) { + + DocumentEdits edits = new DocumentEdits(doc, false); + + if (includeQuotes) { + edits.replace(startOffset, endOffset, "\"classpath:" + resource + "\""); + } + else { + edits.replace(startOffset, endOffset, "classpath:" + resource); + } + + String label = "classpath:" + resource; + + ICompletionProposal proposal = new AnnotationAttributeCompletionProposal(edits, label, label, null, score--); + completions.add(proposal); + } + + } + + private void computeProposalsForSimpleName(IJavaProject project, ASTNode node, Collection completions, int offset, TextDocument doc) { + int startOffset = node.getStartPosition(); + int endOffset = node.getStartPosition() + node.getLength(); + + String unfilteredPrefix = node.toString().substring(0, offset - node.getStartPosition()); + addClasspathResourceProposals(project, doc, startOffset, endOffset, unfilteredPrefix, true, completions); + } + + private void computeProposalsForStringLiteral(IJavaProject project, StringLiteral node, Collection completions, int offset, TextDocument doc) throws BadLocationException { + String prefix = identifyPropertyPrefix(doc.get(node.getStartPosition() + 1, offset - (node.getStartPosition() + 1)), offset - (node.getStartPosition() + 1)); + + int startOffset = offset - prefix.length(); + int endOffset = offset; + + String unfilteredPrefix = node.getLiteralValue().substring(0, offset - (node.getStartPosition() + 1)); + addClasspathResourceProposals(project, doc, startOffset, endOffset, unfilteredPrefix, false, completions); + } + + public 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 String[] findResources(IJavaProject project, String prefix) { + String[] resources = IClasspathUtil.getClasspathResources(project.getClasspath()).stream() + .distinct() + .sorted(new Comparator() { + @Override + public int compare(String o1, String o2) { + return Paths.get(o1).compareTo(Paths.get(o2)); + } + }) + .map(r -> r.replaceAll("\\\\", "/")) + .filter(r -> ("classpath:" + r).contains(prefix)) + .toArray(String[]::new); + + return resources; + } + +} \ No newline at end of file diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/conditionalonresource/test/ConditionalOnResourceCompletionTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/conditionalonresource/test/ConditionalOnResourceCompletionTest.java new file mode 100644 index 0000000000..9caeab563f --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/conditionalonresource/test/ConditionalOnResourceCompletionTest.java @@ -0,0 +1,250 @@ +/******************************************************************************* + * Copyright (c) 2024 Broadcom, 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: + * Broadcom, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.conditionalonresource.test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; + +import org.apache.commons.io.IOUtils; +import org.eclipse.lsp4j.CompletionItem; +import org.eclipse.lsp4j.TextDocumentIdentifier; +import org.gradle.internal.impldep.com.google.common.collect.ImmutableList; +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.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.ide.vscode.boot.app.BootLanguageServerParams; +import org.springframework.ide.vscode.boot.bootiful.AdHocPropertyHarnessTestConf; +import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest; +import org.springframework.ide.vscode.boot.editor.harness.AdHocPropertyHarness; +import org.springframework.ide.vscode.boot.editor.harness.PropertyIndexHarness; +import org.springframework.ide.vscode.boot.index.cache.IndexCache; +import org.springframework.ide.vscode.boot.index.cache.IndexCacheVoid; +import org.springframework.ide.vscode.boot.java.links.SourceLinkFactory; +import org.springframework.ide.vscode.boot.java.links.SourceLinks; +import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache; +import org.springframework.ide.vscode.boot.metadata.ValueProviderRegistry; +import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; +import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver; +import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; +import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService; +import org.springframework.ide.vscode.commons.maven.java.MavenJavaProject; +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 Karthik Sankaranarayanan + */ +@ExtendWith(SpringExtension.class) +@BootLanguageServerTest +@Import({AdHocPropertyHarnessTestConf.class, ConditionalOnResourceCompletionTest.TestConf.class}) +public class ConditionalOnResourceCompletionTest { + + @Autowired private BootLanguageServerHarness harness; + @Autowired private JavaProjectFinder projectFinder; + + private Editor editor; + + @Autowired private PropertyIndexHarness indexHarness; + @Autowired private AdHocPropertyHarness adHocProperties; + + @Configuration + static class TestConf { + + @Bean MavenJavaProject testProject() throws Exception { + return ProjectsHarness.INSTANCE.mavenProject("test-annotation-conditionalonresource"); + } + + @Bean PropertyIndexHarness indexHarness(ValueProviderRegistry valueProviders) { + return new PropertyIndexHarness(valueProviders); + } + + @Bean JavaProjectFinder projectFinder(MavenJavaProject testProject) { + return new JavaProjectFinder() { + + @Override + public Optional find(TextDocumentIdentifier doc) { + return Optional.ofNullable(testProject); + } + + @Override + public Collection all() { + // TODO Auto-generated method stub + return testProject == null ? Collections.emptyList() : ImmutableList.of(testProject); + } + }; + } + + @Bean BootLanguageServerHarness harness(SimpleLanguageServer server, BootLanguageServerParams serverParams, PropertyIndexHarness indexHarness, JavaProjectFinder projectFinder) throws Exception { + return new BootLanguageServerHarness(server, serverParams, indexHarness, projectFinder, LanguageId.JAVA, ".java"); + } + + @Bean BootLanguageServerParams serverParams(SimpleLanguageServer server, JavaProjectFinder projectFinder, ValueProviderRegistry valueProviders, PropertyIndexHarness indexHarness) { + BootLanguageServerParams testDefaults = BootLanguageServerHarness.createTestDefault(server, valueProviders); + return new BootLanguageServerParams( + projectFinder, + ProjectObserver.NULL, + indexHarness.getIndexProvider(), + testDefaults.typeUtilProvider + ); + } + + @Bean IndexCache symbolCache() { + return new IndexCacheVoid(); + } + + @Bean SourceLinks sourceLinks(SimpleTextDocumentService documents, CompilationUnitCache cuCache) { + return SourceLinkFactory.NO_SOURCE_LINKS; + } + + } + + @BeforeEach + public void setup() throws Exception { + IJavaProject testProject = ProjectsHarness.INSTANCE.mavenProject("test-annotation-conditionalonresource"); + harness.useProject(testProject); + harness.intialize(null); + } + + @Test + void testPrefixCompletionWithParamName() throws Exception { + prepareCase("@ConditionalOnResource(resources=\"onClass\")", "@ConditionalOnResource(resources=foo<*>)"); + + assertClasspathCompletions( + "@ConditionalOnResource(resources=\"classpath:org/test/foo\"<*>)"); + } + + @Test + void testEmptyBracketsCompletionWithParamName() throws Exception { + prepareCase("@ConditionalOnResource(resources=\"onClass\")", "@ConditionalOnResource(resources=<*>)"); + + assertClasspathCompletions( + "@ConditionalOnResource(resources=\"classpath:a-random-resource-root.md\"<*>)", + "@ConditionalOnResource(resources=\"classpath:org/random-resource-org.md\"<*>)", + "@ConditionalOnResource(resources=\"classpath:org/test/foo\"<*>)", + "@ConditionalOnResource(resources=\"classpath:org/test/random-resource-org-test.txt\"<*>)"); + } + + @Test + void testEmptyBracketsCompletionWithWrongParamName() throws Exception { + prepareCase("@ConditionalOnResource(resources=\"onClass\")", "@ConditionalOnResource(wrong=foo<*>)"); + assertClasspathCompletions(); + } + + @Test + void testEmptyBracketsCompletionWithoutMandatoryResourcesParam() throws Exception { + prepareCase("@ConditionalOnResource(resources=\"onClass\")", "@ConditionalOnResource(<*>)"); + assertClasspathCompletions(); + } + + @Test + void testEmptyStringLiteralCompletionWithParamName() throws Exception { + prepareCase("@ConditionalOnResource(resources=\"onClass\")", "@ConditionalOnResource(resources=\"<*>\")"); + + assertClasspathCompletions( + "@ConditionalOnResource(resources=\"classpath:a-random-resource-root.md<*>\")", + "@ConditionalOnResource(resources=\"classpath:org/random-resource-org.md<*>\")", + "@ConditionalOnResource(resources=\"classpath:org/test/foo<*>\")", + "@ConditionalOnResource(resources=\"classpath:org/test/random-resource-org-test.txt<*>\")"); + } + + @Test + void testEmptyStringLiteralCompletionWithoutMandatoryResourcesParam() throws Exception { + prepareCase("@ConditionalOnResource(resources=\"onClass\")", "@ConditionalOnResource(\"<*>\")"); + + assertClasspathCompletions(); + } + + @Test + void testComplexResourceNameInPrefixWithParamNameCompletion() throws Exception { + prepareCase("@ConditionalOnResource(resources=\"onClass\")", "@ConditionalOnResource(resources=root.md<*>)"); + + assertClasspathCompletions( + "@ConditionalOnResource(resources=\"classpath:a-random-resource-root.md\"<*>)"); + } + + @Test + void testComplexResourceNameInPrefixWithWrongExtensionAndParamNameCompletion() throws Exception { + prepareCase("@ConditionalOnResource(resources=\"onClass\")", "@ConditionalOnResource(resources=root.xml<*>)"); + + assertClasspathCompletions(); + } + + @Test + void testComplexResourceNameInPrefixWithinQuotesAndParamNameCompletion() throws Exception { + prepareCase("@ConditionalOnResource(resources=\"onClass\")", "@ConditionalOnResource(resources=\"root.md<*>\")"); + + assertClasspathCompletions( + "@ConditionalOnResource(resources=\"classpath:a-random-resource-root.md<*>\")"); + } + + @Test + void testComplexResourceNameWithSlashPrefixAndWithResourcesAndParamNameCompletion() throws Exception { + prepareCase("@ConditionalOnResource(resources=\"onClass\")", "@ConditionalOnResource(resources=\"/root.md<*>\")"); + + assertClasspathCompletions(); + } + + + + + + private void prepareCase(String selectedAnnotation, String annotationStatementBeforeTest) throws Exception { + InputStream resource = this.getClass().getResourceAsStream("/test-projects/test-annotation-conditionalonresource/src/main/java/org/test/TestConditionalOnResourceCompletion.java"); + String content = IOUtils.toString(resource, Charset.defaultCharset()); + + content = content.replace(selectedAnnotation, annotationStatementBeforeTest); + editor = new Editor(harness, content, LanguageId.JAVA); + } + + private void assertClasspathCompletions(String... completedAnnotations) throws Exception { + List completions = editor.getCompletions(); + + List filteredCompletions = completions.stream() + .filter(item -> item.getTextEdit().getLeft().getNewText().contains("classpath")) + .sorted(new Comparator() { + @Override + public int compare(CompletionItem o1, CompletionItem o2) { + return o1.getLabel().compareTo(o2.getLabel()); + } + }) + .toList(); + + assertEquals(completedAnnotations.length, filteredCompletions.size()); + + for (int i = 0; i < completedAnnotations.length; i++) { + CompletionItem completion = filteredCompletions.get(i); + + Editor clonedEditor = editor.clone(); + clonedEditor.apply(completion); + + String expected = completedAnnotations[i]; + if (!clonedEditor.getText().contains(expected)) { + fail("Not found '" + expected +"' in \n" + clonedEditor.getText()); + } + } + } +} \ No newline at end of file diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/conditionalonresource/test/ConditionalOnResourceDefinitionProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/conditionalonresource/test/ConditionalOnResourceDefinitionProviderTest.java new file mode 100644 index 0000000000..62b9f67b43 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/conditionalonresource/test/ConditionalOnResourceDefinitionProviderTest.java @@ -0,0 +1,97 @@ +/******************************************************************************* + * Copyright (c) 2024 Broadcom, 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: + * Broadcom, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.conditionalonresource.test; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.eclipse.lsp4j.LocationLink; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.Range; +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.bootiful.AdHocPropertyHarnessTestConf; +import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest; +import org.springframework.ide.vscode.boot.java.conditionalonresource.ConditionalOnResourceDefinitionProvider; +import org.springframework.ide.vscode.commons.java.IJavaProject; +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.test.context.junit.jupiter.SpringExtension; + +@ExtendWith(SpringExtension.class) +@BootLanguageServerTest +@Import({ AdHocPropertyHarnessTestConf.class, ConditionalOnResourceCompletionTest.TestConf.class }) +public class ConditionalOnResourceDefinitionProviderTest { + + @Autowired + private BootLanguageServerHarness harness; + @Autowired + private IJavaProject testProject; + + private Set createdFiles = new HashSet<>(); + + @BeforeEach + public void setup() throws Exception { + harness.intialize(null); + } + + @AfterEach + public void tearDown() throws Exception { + for (Path f : createdFiles) { + Files.deleteIfExists(f); + } + createdFiles.clear(); + } + + private Path projectFile(String relativePath, String content) throws IOException { + Path projectPath = Paths.get(testProject.getLocationUri()); + Path filePath = projectPath.resolve(relativePath); + Files.createDirectories(filePath.getParent()); + Files.write(filePath, content.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE); + createdFiles.add(filePath); + return filePath; + } + + @Test + void testFindClasspathResource() throws Exception { + Path randomResourceFilePath = projectFile("src/main/java/a-random-resource-root.md", ""); + Editor editor = harness.newEditor(LanguageId.JAVA, """ + package org.test; + import org.springframework.boot.autoconfigure.condition.ConditionalOnResource; + import org.springframework.context.annotation.Configuration; + + @Configuration + @ConditionalOnResource("classpath:a-random-resource-root.md") + public class TestConditionalOnResourceCompletion { + private String value1; + }"""); + + LocationLink expectedLocation = new LocationLink(randomResourceFilePath.toUri().toASCIIString(), + new Range(new Position(0, 0), new Position(0, 0)), + new Range(new Position(0, 0), new Position(0, 0)), + new Range(new Position(5, 23), new Position(5, 60))); + + editor.assertLinkTargets("classpath:a-random-resource-root.md", List.of(expectedLocation)); + } + +} \ No newline at end of file diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-conditionalonresource/mvnw b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-conditionalonresource/mvnw new file mode 100755 index 0000000000..02217b1ecb --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-conditionalonresource/mvnw @@ -0,0 +1,233 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # + # Look for the Apple JDKs first to preserve the existing behaviour, and then look + # for the new JDKs provided by Oracle. + # + if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then + # + # Apple JDKs + # + export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then + # + # Apple JDKs + # + export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then + # + # Oracle JDKs + # + export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then + # + # Apple JDKs + # + export JAVA_HOME=`/usr/libexec/java_home` + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Migwn, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + # TODO classpath? +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + local basedir=$(pwd) + local wdir=$(pwd) + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + wdir=$(cd "$wdir/.."; pwd) + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)} +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} "$@" diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-conditionalonresource/mvnw.cmd b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-conditionalonresource/mvnw.cmd new file mode 100644 index 0000000000..4b98b78c48 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-conditionalonresource/mvnw.cmd @@ -0,0 +1,145 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +set MAVEN_CMD_LINE_ARGS=%* + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" + +set WRAPPER_JAR="".\.mvn\wrapper\maven-wrapper.jar"" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS% +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% \ No newline at end of file diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-conditionalonresource/pom.xml b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-conditionalonresource/pom.xml new file mode 100644 index 0000000000..4bd18ab778 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-conditionalonresource/pom.xml @@ -0,0 +1,56 @@ + + + 4.0.0 + + com.example + test-annotation-conditionalonresource + 0.0.1-SNAPSHOT + jar + + test-scope-annotation + Test projects for spring boot java language server - scope annotations + + + org.springframework.boot + spring-boot-starter-parent + 1.4.3.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.2.0 + + 8 + false + + + + + + diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-conditionalonresource/src/main/java/.hiddenresource b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-conditionalonresource/src/main/java/.hiddenresource new file mode 100644 index 0000000000..e69de29bb2 diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-conditionalonresource/src/main/java/a-random-resource-root.md b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-conditionalonresource/src/main/java/a-random-resource-root.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-conditionalonresource/src/main/java/org/random-resource-org.md b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-conditionalonresource/src/main/java/org/random-resource-org.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-conditionalonresource/src/main/java/org/test/TestAnnotationsConditionalOnResourceApplication.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-conditionalonresource/src/main/java/org/test/TestAnnotationsConditionalOnResourceApplication.java new file mode 100644 index 0000000000..9eb3ddf2a7 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-conditionalonresource/src/main/java/org/test/TestAnnotationsConditionalOnResourceApplication.java @@ -0,0 +1,12 @@ +package org.test; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class TestAnnotationsConditionalOnResourceApplication { + + public static void main(String[] args) { + SpringApplication.run(TestAnnotationsConditionalOnResourceApplication.class, args); + } +} \ No newline at end of file diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-conditionalonresource/src/main/java/org/test/TestConditionalOnResourceCompletion.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-conditionalonresource/src/main/java/org/test/TestConditionalOnResourceCompletion.java new file mode 100644 index 0000000000..4032fee132 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-conditionalonresource/src/main/java/org/test/TestConditionalOnResourceCompletion.java @@ -0,0 +1,14 @@ +package org.test; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnResource; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConditionalOnResource(resources="onClass") +public class TestConditionalOnResourceCompletion { + + private String value; + + public void method() { + } +} \ No newline at end of file diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-conditionalonresource/src/main/java/org/test/foo b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-conditionalonresource/src/main/java/org/test/foo new file mode 100644 index 0000000000..e69de29bb2 diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-conditionalonresource/src/main/java/org/test/random-resource-org-test.txt b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-conditionalonresource/src/main/java/org/test/random-resource-org-test.txt new file mode 100644 index 0000000000..e69de29bb2