Merge branch 'main' of https://github.com/spring-projects/sts4 into main
This commit is contained in:
@@ -79,6 +79,8 @@ import org.eclipse.lsp4j.Hover;
|
||||
import org.eclipse.lsp4j.HoverParams;
|
||||
import org.eclipse.lsp4j.InitializeParams;
|
||||
import org.eclipse.lsp4j.InitializeResult;
|
||||
import org.eclipse.lsp4j.InlayHint;
|
||||
import org.eclipse.lsp4j.InlayHintParams;
|
||||
import org.eclipse.lsp4j.Location;
|
||||
import org.eclipse.lsp4j.LocationLink;
|
||||
import org.eclipse.lsp4j.MarkupContent;
|
||||
@@ -681,6 +683,12 @@ public class LanguageServerHarness {
|
||||
params.setTextDocument(document.getId());
|
||||
return getServer().getTextDocumentService().codeLens(params).get();
|
||||
}
|
||||
|
||||
public List<InlayHint> getInlayHints(TextDocumentInfo document) throws Exception {
|
||||
InlayHintParams params = new InlayHintParams();
|
||||
params.setTextDocument(document.getId());
|
||||
return getServer().getTextDocumentService().inlayHint(params).get();
|
||||
}
|
||||
|
||||
public List<? extends DocumentHighlight> getDocumentHighlights(TextDocumentIdentifier docId, Position cursor) throws InterruptedException, ExecutionException {
|
||||
return getServer().getTextDocumentService().documentHighlight(new DocumentHighlightParams(docId, cursor)).get();
|
||||
|
||||
@@ -194,6 +194,13 @@
|
||||
<artifactId>commons-util</artifactId>
|
||||
<version>1.58.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Cron expression descriptor library -->
|
||||
<dependency>
|
||||
<groupId>com.cronutils</groupId>
|
||||
<artifactId>cron-utils</artifactId>
|
||||
<version>9.2.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<profiles>
|
||||
|
||||
@@ -30,6 +30,9 @@ import org.springframework.ide.vscode.boot.java.beans.NamedCompletionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.beans.ProfileCompletionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.beans.QualifierCompletionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.beans.ResourceCompletionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.conditionalonresource.ConditionalOnResourceCompletionProcessor;
|
||||
import org.springframework.ide.vscode.boot.java.contextconfiguration.ContextConfigurationProcessor;
|
||||
import org.springframework.ide.vscode.boot.java.cron.CronExpressionCompletionProvider;
|
||||
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;
|
||||
@@ -40,8 +43,6 @@ import org.springframework.ide.vscode.boot.java.snippets.JavaSnippetManager;
|
||||
import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
|
||||
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.ConditionalOnResourceCompletionProcessor;
|
||||
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;
|
||||
@@ -132,6 +133,8 @@ public class BootJavaCompletionEngineConfigurer {
|
||||
|
||||
providers.put(Annotations.NAMED_JAKARTA, new AnnotationAttributeCompletionProcessor(javaProjectFinder, Map.of("value", new NamedCompletionProvider(springIndex))));
|
||||
providers.put(Annotations.NAMED_JAVAX, new AnnotationAttributeCompletionProcessor(javaProjectFinder, Map.of("value", new NamedCompletionProvider(springIndex))));
|
||||
|
||||
providers.put(Annotations.SCHEDULED, new AnnotationAttributeCompletionProcessor(javaProjectFinder, Map.of("cron", new CronExpressionCompletionProvider())));
|
||||
|
||||
return new BootJavaCompletionEngine(cuCache, providers, snippetManager);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.ide.vscode.boot.java.cron.CronExpressionsInlayHintsProvider;
|
||||
import org.springframework.ide.vscode.boot.java.cron.CronReconciler;
|
||||
import org.springframework.ide.vscode.boot.java.cron.CronSemanticTokens;
|
||||
import org.springframework.ide.vscode.boot.java.cron.JdtCronReconciler;
|
||||
@@ -139,6 +140,10 @@ public class JdtConfig {
|
||||
return new JdtDataQueriesInlayHintsProvider(semanticTokensProvider);
|
||||
}
|
||||
|
||||
@Bean CronExpressionsInlayHintsProvider cronExpressionsInlayHintsProvider() {
|
||||
return new CronExpressionsInlayHintsProvider();
|
||||
}
|
||||
|
||||
@Bean JdtQueryDocHighlightsProvider jdtDocHighlightsProvider(JdtDataQuerySemanticTokensProvider semanticTokensProvider) {
|
||||
return new JdtQueryDocHighlightsProvider(semanticTokensProvider);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ package org.springframework.ide.vscode.boot.java.annotations;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@@ -114,34 +115,34 @@ public class AnnotationAttributeCompletionProcessor implements CompletionProvide
|
||||
/**
|
||||
* create the concrete completion proposal
|
||||
*/
|
||||
private void createCompletionProposals(IJavaProject project, TextDocument doc, ASTNode node, String attributeName, Collection<ICompletionProposal> completions, int startOffset, int endOffset,
|
||||
String filterPrefix, Function<String, String> createReplacementText) {
|
||||
private void createCompletionProposals(IJavaProject project, TextDocument doc, ASTNode node, String attributeName,
|
||||
Collection<ICompletionProposal> completions, int startOffset, int endOffset, String filterPrefix,
|
||||
Function<String, String> createReplacementText) {
|
||||
|
||||
Set<String> alreadyMentionedValues = alreadyMentionedValues(node);
|
||||
|
||||
AnnotationAttributeCompletionProvider completionProvider = this.completionProviders.get(attributeName);
|
||||
if (completionProvider != null) {
|
||||
List<String> candidates = completionProvider.getCompletionCandidates(project);
|
||||
|
||||
List<String> filteredCandidates = candidates.stream()
|
||||
// .filter(candidate -> candidate.toLowerCase().startsWith(filterPrefix.toLowerCase()))
|
||||
.filter(candidate -> candidate.toLowerCase().contains(filterPrefix.toLowerCase()))
|
||||
.filter(candidate -> !alreadyMentionedValues.contains(candidate))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
double score = filteredCandidates.size();
|
||||
for (String candidate : filteredCandidates) {
|
||||
|
||||
Map<String, String> proposals = completionProvider.getCompletionCandidates(project);
|
||||
Map<String, String> filteredProposals = proposals.entrySet().stream()
|
||||
.filter(candidate -> candidate.getKey().toLowerCase().contains(filterPrefix.toLowerCase()))
|
||||
.filter(candidate -> !alreadyMentionedValues.contains(candidate.getKey()))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (u, v) -> u, LinkedHashMap::new));
|
||||
double score = filteredProposals.size();
|
||||
for (Map.Entry<String, String> entry : filteredProposals.entrySet()) {
|
||||
String candidate = entry.getKey();
|
||||
DocumentEdits edits = new DocumentEdits(doc, false);
|
||||
edits.replace(startOffset, endOffset, createReplacementText.apply(candidate));
|
||||
|
||||
AnnotationAttributeCompletionProposal proposal = new AnnotationAttributeCompletionProposal(edits, candidate, candidate, null, score--);
|
||||
|
||||
AnnotationAttributeCompletionProposal proposal = new AnnotationAttributeCompletionProposal(edits,
|
||||
candidate, entry.getValue(), null, score--);
|
||||
completions.add(proposal);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// internal computation of the right positions, prefixes, etc.
|
||||
//
|
||||
|
||||
@@ -10,12 +10,12 @@
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.annotations;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
|
||||
public interface AnnotationAttributeCompletionProvider {
|
||||
|
||||
List<String> getCompletionCandidates(IJavaProject project);
|
||||
Map<String, String> getCompletionCandidates(IJavaProject project);
|
||||
|
||||
}
|
||||
|
||||
@@ -11,7 +11,9 @@
|
||||
package org.springframework.ide.vscode.boot.java.beans;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
|
||||
import org.springframework.ide.vscode.boot.java.annotations.AnnotationAttributeCompletionProvider;
|
||||
@@ -235,11 +237,11 @@ public class DependsOnCompletionProcessor implements AnnotationAttributeCompleti
|
||||
// }
|
||||
|
||||
@Override
|
||||
public List<String> getCompletionCandidates(IJavaProject project) {
|
||||
public Map<String, String> getCompletionCandidates(IJavaProject project) {
|
||||
return Arrays.stream(this.springIndex.getBeansOfProject(project.getElementName()))
|
||||
.map(bean -> bean.getName())
|
||||
.distinct()
|
||||
.toList();
|
||||
.collect(Collectors.toMap(key -> key, value -> value, (u, v) -> u, LinkedHashMap::new));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -11,7 +11,9 @@
|
||||
package org.springframework.ide.vscode.boot.java.beans;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
|
||||
@@ -32,7 +34,7 @@ public class NamedCompletionProvider implements AnnotationAttributeCompletionPro
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getCompletionCandidates(IJavaProject project) {
|
||||
public Map<String, String> getCompletionCandidates(IJavaProject project) {
|
||||
|
||||
Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName());
|
||||
|
||||
@@ -40,7 +42,7 @@ public class NamedCompletionProvider implements AnnotationAttributeCompletionPro
|
||||
findAllNamedValues(beans),
|
||||
Arrays.stream(beans).map(bean -> bean.getName()))
|
||||
.distinct()
|
||||
.toList();
|
||||
.collect(Collectors.toMap(key -> key, value -> value, (u, v) -> u, LinkedHashMap::new));
|
||||
}
|
||||
|
||||
private Stream<String> findAllNamedValues(Bean[] beans) {
|
||||
|
||||
@@ -11,7 +11,9 @@
|
||||
package org.springframework.ide.vscode.boot.java.beans;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
|
||||
@@ -32,13 +34,13 @@ public class ProfileCompletionProvider implements AnnotationAttributeCompletionP
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getCompletionCandidates(IJavaProject project) {
|
||||
public Map<String, String> getCompletionCandidates(IJavaProject project) {
|
||||
|
||||
Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName());
|
||||
|
||||
return findAllProfiles(beans)
|
||||
.distinct()
|
||||
.toList();
|
||||
.collect(Collectors.toMap(key -> key, value -> value, (u, v) -> u, LinkedHashMap::new));
|
||||
}
|
||||
|
||||
private Stream<String> findAllProfiles(Bean[] beans) {
|
||||
|
||||
@@ -11,7 +11,9 @@
|
||||
package org.springframework.ide.vscode.boot.java.beans;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
|
||||
@@ -32,7 +34,7 @@ public class QualifierCompletionProvider implements AnnotationAttributeCompletio
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getCompletionCandidates(IJavaProject project) {
|
||||
public Map<String, String> getCompletionCandidates(IJavaProject project) {
|
||||
|
||||
Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName());
|
||||
|
||||
@@ -40,7 +42,7 @@ public class QualifierCompletionProvider implements AnnotationAttributeCompletio
|
||||
findAllQualifiers(beans),
|
||||
Arrays.stream(beans).map(bean -> bean.getName()))
|
||||
.distinct()
|
||||
.toList();
|
||||
.collect(Collectors.toMap(key -> key, value -> value, (u, v) -> u, LinkedHashMap::new));
|
||||
}
|
||||
|
||||
private Stream<String> findAllQualifiers(Bean[] beans) {
|
||||
|
||||
@@ -11,7 +11,9 @@
|
||||
package org.springframework.ide.vscode.boot.java.beans;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
|
||||
import org.springframework.ide.vscode.boot.java.annotations.AnnotationAttributeCompletionProvider;
|
||||
@@ -30,13 +32,13 @@ public class ResourceCompletionProvider implements AnnotationAttributeCompletion
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getCompletionCandidates(IJavaProject project) {
|
||||
public Map<String, String> getCompletionCandidates(IJavaProject project) {
|
||||
|
||||
Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName());
|
||||
|
||||
return Arrays.stream(beans).map(bean -> bean.getName())
|
||||
.distinct()
|
||||
.toList();
|
||||
.collect(Collectors.toMap(key -> key, value -> value, (u, v) -> u, LinkedHashMap::new));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,7 +12,9 @@ package org.springframework.ide.vscode.boot.java.conditionalonresource;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.ide.vscode.boot.java.annotations.AnnotationAttributeCompletionProvider;
|
||||
import org.springframework.ide.vscode.commons.java.IClasspathUtil;
|
||||
@@ -23,8 +25,8 @@ import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
*/
|
||||
public class ConditionalOnResourceCompletionProcessor implements AnnotationAttributeCompletionProvider {
|
||||
|
||||
private List<String> findResources(IJavaProject project) {
|
||||
List<String> resources = IClasspathUtil.getClasspathResources(project.getClasspath()).stream()
|
||||
private Map<String, String> findResources(IJavaProject project) {
|
||||
Map<String, String> resources = IClasspathUtil.getClasspathResources(project.getClasspath()).stream()
|
||||
.distinct()
|
||||
.sorted(new Comparator<String>() {
|
||||
@Override
|
||||
@@ -34,13 +36,13 @@ public class ConditionalOnResourceCompletionProcessor implements AnnotationAttri
|
||||
})
|
||||
.map(r -> r.replaceAll("\\\\", "/"))
|
||||
.map(r -> "classpath:" + r)
|
||||
.toList();
|
||||
.collect(Collectors.toMap(key -> key, value -> value, (u, v) -> u, LinkedHashMap::new));
|
||||
|
||||
return resources;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getCompletionCandidates(IJavaProject project) {
|
||||
public Map<String, String> getCompletionCandidates(IJavaProject project) {
|
||||
return findResources(project);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.springframework.ide.vscode.boot.java.cron;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.ide.vscode.boot.java.annotations.AnnotationAttributeCompletionProvider;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
|
||||
public class CronExpressionCompletionProvider implements AnnotationAttributeCompletionProvider {
|
||||
|
||||
private static final Map<String, String> CRON_EXPRESSIONS_MAP = new LinkedHashMap<>();
|
||||
|
||||
static {
|
||||
CRON_EXPRESSIONS_MAP.put("0 0 * * * *", "every hour");
|
||||
CRON_EXPRESSIONS_MAP.put("0 0 * * * 1-5", "every hour every day between Monday and Friday");
|
||||
CRON_EXPRESSIONS_MAP.put("0 * * * * *", "every minute");
|
||||
CRON_EXPRESSIONS_MAP.put("0 */5 * * * *", "every 5 minutes");
|
||||
CRON_EXPRESSIONS_MAP.put("0 0 */6 * * *", "every 6 hours at minute 0");
|
||||
CRON_EXPRESSIONS_MAP.put("0 0 * * * SUN", "every hour at Sunday day");
|
||||
CRON_EXPRESSIONS_MAP.put("0 0 0 * * *", "at 00:00");
|
||||
CRON_EXPRESSIONS_MAP.put("0 0 0 * * SAT,SUN", "at 00:00 on Saturday and Sunday");
|
||||
CRON_EXPRESSIONS_MAP.put("0 0 0 * * 6,0", "at 00:00 at Saturday and Sunday days");
|
||||
CRON_EXPRESSIONS_MAP.put("0 0 0 1-7 * SUN", "at 00:00 every day between 1 and 7 at Sunday day");
|
||||
CRON_EXPRESSIONS_MAP.put("0 0 0 1 * *", "at 00:00 at 1 day");
|
||||
CRON_EXPRESSIONS_MAP.put("0 0 0 1 1 *", "at 00:00 at 1 day at January month");
|
||||
CRON_EXPRESSIONS_MAP.put("0 0 8-18 * * *", "every hour between 8 and 18");
|
||||
CRON_EXPRESSIONS_MAP.put("0 0 9 * * MON", "at 09:00 at Monday day");
|
||||
CRON_EXPRESSIONS_MAP.put("0 0 10 * * *", "at 10:00");
|
||||
CRON_EXPRESSIONS_MAP.put("0 30 9 * JAN MON", "at 09:30 at January month at Monday day");
|
||||
CRON_EXPRESSIONS_MAP.put("10 * * * * *", "every minute at second 10");
|
||||
CRON_EXPRESSIONS_MAP.put("0 0 8-10 * * *", "every hour between 8 and 10");
|
||||
CRON_EXPRESSIONS_MAP.put("0 0/30 8-10 * * *", "every 30 minutes every hour between 8 and 10");
|
||||
CRON_EXPRESSIONS_MAP.put("0 0 0 L * *", " at 00:00 last day of month");
|
||||
CRON_EXPRESSIONS_MAP.put("0 0 0 1W * *", "at 00:00 the nearest weekday to the 1 of the month");
|
||||
CRON_EXPRESSIONS_MAP.put("0 0 0 * * THUL", "at 00:00 last Thursday of every month");
|
||||
CRON_EXPRESSIONS_MAP.put("0 0 0 ? * 5#2", "at 00:00 Friday 2 of every month");
|
||||
CRON_EXPRESSIONS_MAP.put("0 0 0 ? * MON#1", "at 00:00 Monday 1 of every month");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Map<String, String> getCompletionCandidates(IJavaProject project) {
|
||||
return CRON_EXPRESSIONS_MAP;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package org.springframework.ide.vscode.boot.java.cron;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTVisitor;
|
||||
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.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.jdt.core.dom.TextBlock;
|
||||
import org.eclipse.lsp4j.InlayHint;
|
||||
import org.eclipse.lsp4j.InlayHintKind;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.boot.java.Annotations;
|
||||
import org.springframework.ide.vscode.boot.java.JdtInlayHintsProvider;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.util.Collector;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
import org.springframework.scheduling.support.CronExpression;
|
||||
|
||||
import com.cronutils.descriptor.CronDescriptor;
|
||||
import com.cronutils.model.definition.CronDefinition;
|
||||
import com.cronutils.model.definition.CronDefinitionBuilder;
|
||||
import com.cronutils.parser.CronParser;
|
||||
|
||||
import static com.cronutils.model.CronType.SPRING;
|
||||
|
||||
public class CronExpressionsInlayHintsProvider implements JdtInlayHintsProvider {
|
||||
|
||||
protected static Logger logger = LoggerFactory.getLogger(CronExpressionsInlayHintsProvider.class);
|
||||
|
||||
private static final String SCHEDULED = "Scheduled";
|
||||
|
||||
public record EmbeddedCronExpression(Expression expression, String text, int offset) {
|
||||
};
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(IJavaProject project) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ASTVisitor getInlayHintsComputer(IJavaProject project, TextDocument doc, CompilationUnit cu,
|
||||
Collector<InlayHint> collector) {
|
||||
return new ASTVisitor() {
|
||||
|
||||
@Override
|
||||
public boolean visit(NormalAnnotation node) {
|
||||
EmbeddedCronExpression cron = extractCronExpression(node);
|
||||
if (cron != null) {
|
||||
processCron(project, doc, collector, cron, node);
|
||||
}
|
||||
return super.visit(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(SingleMemberAnnotation node) {
|
||||
EmbeddedCronExpression cron = extractCronExpression(node);
|
||||
if (cron != null) {
|
||||
processCron(project, doc, collector, cron, node);
|
||||
}
|
||||
return super.visit(node);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
private void processCron(IJavaProject project, TextDocument doc, Collector<InlayHint> collector,
|
||||
EmbeddedCronExpression cronExp, Annotation node) {
|
||||
boolean isValidExpression = CronExpression.isValidExpression(cronExp.text());
|
||||
|
||||
try {
|
||||
if (isValidExpression) {
|
||||
CronDefinition cronDefinition = CronDefinitionBuilder.instanceDefinitionFor(SPRING);
|
||||
CronParser parser = new CronParser(cronDefinition);
|
||||
CronDescriptor descriptor = CronDescriptor.instance(Locale.US);
|
||||
String cronDescription = descriptor.describe(parser.parse(cronExp.text().toUpperCase()));
|
||||
|
||||
InlayHint hint = new InlayHint();
|
||||
hint.setKind(InlayHintKind.Type);
|
||||
hint.setLabel(Either.forLeft(cronDescription));
|
||||
hint.setTooltip(cronDescription);
|
||||
hint.setPaddingLeft(true);
|
||||
hint.setPaddingRight(true);
|
||||
hint.setPosition(doc.toPosition(node.getStartPosition() + node.getLength()));
|
||||
collector.accept(hint);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
public static EmbeddedCronExpression extractCronExpression(SingleMemberAnnotation a) {
|
||||
if (isScheduledAnnotation(a)) {
|
||||
EmbeddedCronExpression expression = extractEmbeddedExpression(a.getValue(), a);
|
||||
return expression == null ? null
|
||||
: new EmbeddedCronExpression(expression.expression(), expression.text(), expression.offset());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static EmbeddedCronExpression extractCronExpression(NormalAnnotation a) {
|
||||
Expression cronExpression = null;
|
||||
if (isScheduledAnnotation(a)) {
|
||||
for (Object value : a.values()) {
|
||||
if (value instanceof MemberValuePair) {
|
||||
MemberValuePair pair = (MemberValuePair) value;
|
||||
String name = pair.getName().getFullyQualifiedName();
|
||||
if ("cron".equals(name)) {
|
||||
cronExpression = pair.getValue();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cronExpression != null) {
|
||||
EmbeddedCronExpression e = extractEmbeddedExpression(cronExpression, a);
|
||||
if (e != null) {
|
||||
return new EmbeddedCronExpression(e.expression(), e.text(), e.offset());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static EmbeddedCronExpression extractEmbeddedExpression(Expression valueExp, Annotation node) {
|
||||
String text = null;
|
||||
int offset = 0;
|
||||
if (valueExp instanceof StringLiteral sl) {
|
||||
text = sl.getEscapedValue();
|
||||
text = text.substring(1, text.length() - 1);
|
||||
offset = sl.getStartPosition() + 1; // +1 to skip over opening "
|
||||
} else if (valueExp instanceof TextBlock tb) {
|
||||
text = tb.getEscapedValue();
|
||||
text = text.substring(3, text.length() - 3).trim();
|
||||
offset = tb.getStartPosition() + 3; // +3 to skip over opening """
|
||||
}
|
||||
return text == null ? null : new EmbeddedCronExpression(valueExp, text, offset);
|
||||
}
|
||||
|
||||
static boolean isScheduledAnnotation(Annotation a) {
|
||||
return Annotations.SCHEDULED.equals(a.getTypeName().getFullyQualifiedName())
|
||||
|| SCHEDULED.equals(a.getTypeName().getFullyQualifiedName());
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,8 @@
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.scope;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.ide.vscode.boot.java.annotations.AnnotationAttributeCompletionProvider;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
@@ -20,18 +21,20 @@ import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
*/
|
||||
public class ScopeCompletionProcessor implements AnnotationAttributeCompletionProvider {
|
||||
|
||||
private static final List<String> SCOPE_COMPLETIONS = List.of(
|
||||
"application",
|
||||
"globalSession",
|
||||
"prototype",
|
||||
"request",
|
||||
"session",
|
||||
"singleton",
|
||||
"websocket"
|
||||
);
|
||||
private static final Map<String, String> SCOPE_COMPLETIONS = new LinkedHashMap<>();
|
||||
|
||||
static {
|
||||
SCOPE_COMPLETIONS.put("application", "application");
|
||||
SCOPE_COMPLETIONS.put("globalSession", "globalSession");
|
||||
SCOPE_COMPLETIONS.put("prototype", "prototype");
|
||||
SCOPE_COMPLETIONS.put("request", "request");
|
||||
SCOPE_COMPLETIONS.put("session", "session");
|
||||
SCOPE_COMPLETIONS.put("singleton", "singleton");
|
||||
SCOPE_COMPLETIONS.put("websocket", "websocket");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getCompletionCandidates(IJavaProject project) {
|
||||
public Map<String, String> getCompletionCandidates(IJavaProject project) {
|
||||
return SCOPE_COMPLETIONS;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ 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.Name;
|
||||
import org.eclipse.jdt.core.dom.QualifiedName;
|
||||
import org.eclipse.jdt.core.dom.SimpleName;
|
||||
import org.eclipse.jdt.core.dom.StringLiteral;
|
||||
@@ -75,6 +76,11 @@ public class ValueCompletionProcessor implements CompletionProvider {
|
||||
|
||||
IJavaProject project = optionalProject.get();
|
||||
|
||||
// in case the node is embedded in an qualified name, e.g. "file.txt", use the fully qualified node instead just a part
|
||||
if (node instanceof Name && node.getParent() instanceof QualifiedName) {
|
||||
node = node.getParent();
|
||||
}
|
||||
|
||||
// case: @Value(<*>)
|
||||
if (node == annotation && doc.get(offset - 1, 2).endsWith("()")) {
|
||||
List<Match<PropertyInfo>> matches = findMatches("", doc);
|
||||
@@ -95,20 +101,16 @@ public class ValueCompletionProcessor implements CompletionProvider {
|
||||
addClasspathResourceProposals(project, doc, offset, offset, "", true, completions);
|
||||
}
|
||||
// case: @Value(prefix<*>)
|
||||
else if (node instanceof SimpleName && node.getParent() instanceof Annotation) {
|
||||
else if (node instanceof Name && node.getParent() instanceof Annotation) {
|
||||
computeProposalsForSimpleName(project, node, completions, offset, doc);
|
||||
}
|
||||
// case: @Value(file.ext<*>) - the "." causes a QualifierNode to be generated
|
||||
else if (node instanceof SimpleName && node.getParent() instanceof QualifiedName && node.getParent().getParent() instanceof Annotation) {
|
||||
computeProposalsForSimpleName(project, node.getParent(), completions, offset, doc);
|
||||
}
|
||||
// case: @Value(value=<*>)
|
||||
else if (node instanceof SimpleName && node.getParent() instanceof MemberValuePair
|
||||
else if (node instanceof Name && node.getParent() instanceof MemberValuePair
|
||||
&& "value".equals(((MemberValuePair)node.getParent()).getName().toString())) {
|
||||
computeProposalsForSimpleName(project, node, completions, offset, doc);
|
||||
}
|
||||
// case: @Value(value=<*>)
|
||||
else if (node instanceof SimpleName && node.getParent() instanceof QualifiedName && node.getParent().getParent() instanceof MemberValuePair
|
||||
else if (node instanceof Name && node.getParent() instanceof QualifiedName && node.getParent().getParent() instanceof MemberValuePair
|
||||
&& "value".equals(((MemberValuePair)node.getParent().getParent()).getName().toString())) {
|
||||
computeProposalsForSimpleName(project, node.getParent(), completions, offset, doc);
|
||||
}
|
||||
@@ -131,29 +133,6 @@ public class ValueCompletionProcessor implements CompletionProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private void addClasspathResourceProposals(IJavaProject project, TextDocument doc, int startOffset, int endOffset, String prefix, boolean includeQuotes, Collection<ICompletionProposal> 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<ICompletionProposal> completions, int offset, TextDocument doc) {
|
||||
String prefix = identifyPropertyPrefix(node.toString(), offset - node.getStartPosition());
|
||||
|
||||
@@ -253,15 +232,15 @@ public class ValueCompletionProcessor implements CompletionProvider {
|
||||
|
||||
private List<Match<PropertyInfo>> findMatches(String prefix, IDocument doc) {
|
||||
FuzzyMap<PropertyInfo> index = indexProvider.getIndex(doc).getProperties();
|
||||
List<Match<PropertyInfo>> matches =index.find(camelCaseToHyphens(prefix));
|
||||
List<Match<PropertyInfo>> matches = index.find(camelCaseToHyphens(prefix));
|
||||
|
||||
//First the 'real' properties.
|
||||
// First the 'real' properties.
|
||||
Set<String> suggestedKeys = new HashSet<>();
|
||||
for (Match<PropertyInfo> m : matches) {
|
||||
suggestedKeys.add(m.data.getId());
|
||||
}
|
||||
|
||||
//Then also add 'ad-hoc' properties (see https://www.pivotaltracker.com/story/show/153107266).
|
||||
// Then also add 'ad-hoc' properties
|
||||
Optional<IJavaProject> p = projectFinder.find(new TextDocumentIdentifier(doc.getUri()));
|
||||
if (p.isPresent()) {
|
||||
index = adHocIndexProvider.getIndex(p.get());
|
||||
@@ -274,6 +253,29 @@ public class ValueCompletionProcessor implements CompletionProvider {
|
||||
return matches;
|
||||
}
|
||||
|
||||
private void addClasspathResourceProposals(IJavaProject project, TextDocument doc, int startOffset, int endOffset, String prefix, boolean includeQuotes, Collection<ICompletionProposal> 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 String[] findResources(IJavaProject project, String prefix) {
|
||||
String[] resources = IClasspathUtil.getClasspathResources(project.getClasspath()).stream()
|
||||
.distinct()
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.springframework.ide.vscode.commons.util.Renderable;
|
||||
public class ValuePropertyKeyProposal extends ScoreableProposal {
|
||||
|
||||
private static final String EMPTY_DETAIL = "";
|
||||
|
||||
private DocumentEdits edits;
|
||||
private String label;
|
||||
private String detail;
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
/*******************************************************************************
|
||||
* 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.cron;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
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.TextDocumentIdentifier;
|
||||
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.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
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 Udayani V
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@BootLanguageServerTest
|
||||
@Import(SymbolProviderTestConf.class)
|
||||
public class CronExpressionCompletionProviderTest {
|
||||
@Autowired private BootLanguageServerHarness harness;
|
||||
@Autowired private JavaProjectFinder projectFinder;
|
||||
@Autowired private SpringSymbolIndex indexer;
|
||||
|
||||
private File directory;
|
||||
private IJavaProject project;
|
||||
private String tempJavaDocUri;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() throws Exception {
|
||||
harness.intialize(null);
|
||||
|
||||
directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotations/").toURI());
|
||||
|
||||
String projectDir = directory.toURI().toString();
|
||||
project = projectFinder.find(new TextDocumentIdentifier(projectDir)).get();
|
||||
|
||||
CompletableFuture<Void> initProject = indexer.waitOperation();
|
||||
initProject.get(5, TimeUnit.SECONDS);
|
||||
|
||||
tempJavaDocUri = directory.toPath().resolve("src/main/java/org/test/TempClass.java").toUri().toString();
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCronExpressionCompletionWithoutPrefix() throws Exception {
|
||||
assertCompletions("@Scheduled(<*>)", new String[] {}, 0, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCronExpressionCompletionInsideOfQuotesWithoutPrefix() throws Exception {
|
||||
assertCompletions("@Scheduled(\"<*>\")", new String[] {}, 0, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCronExpressionCompletionWithoutQuotesWithPrefix() throws Exception {
|
||||
assertCompletions("@Scheduled(0<*>)", new String[] {}, 0, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCronExpressionCompletionInsideOfQuotesWithPrefix() throws Exception {
|
||||
assertCompletions("@Scheduled(\"10<*>\")", new String[] {}, 0, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCronExpressionCompletionWithoutQuotesWithAttributeName() throws Exception {
|
||||
assertCompletions("@Scheduled(cron=<*>)", 24, "@Scheduled(cron=\"0 0 * * * *\"<*>)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCronExpressionCompletionWithAttributeNameAndPrefix() throws Exception {
|
||||
assertCompletions("@Scheduled(cron=\"0<*>\")", 24, "@Scheduled(cron=\"0 0 * * * *<*>\")");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCronExpressionCompletionWithFilteredMatches() throws Exception {
|
||||
assertCompletions("@Scheduled(cron=\"MON<*>\")", 3, "@Scheduled(cron=\"0 0 9 * * MON<*>\")");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCronExpressionCompletionPrefixWithFilteredMatches() throws Exception {
|
||||
assertCompletions("@Scheduled(cron=\"W<*>\")", 1, "@Scheduled(cron=\"0 0 0 1W * *<*>\")");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCronExpressionCompletionWithNoMatches() throws Exception {
|
||||
assertCompletions("@Scheduled(cron=\"WED<*>\")", 0, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCronExpressionCompletionWithMultipleAttributes() throws Exception {
|
||||
assertCompletions("@Scheduled(cron=\"JAN<*>\", fixedDelay = 1000)", 1, "@Scheduled(cron=\"0 30 9 * JAN MON<*>\", fixedDelay = 1000)");
|
||||
}
|
||||
|
||||
private void assertCompletions(String completionLine, int noOfExpectedCompletions, String expectedCompletedLine) throws Exception {
|
||||
assertCompletions(completionLine, noOfExpectedCompletions, null, 0, expectedCompletedLine);
|
||||
}
|
||||
|
||||
private void assertCompletions(String completionLine, String[] expectedCompletions, int chosenCompletion, String expectedCompletedLine) throws Exception {
|
||||
assertCompletions(completionLine, expectedCompletions.length, expectedCompletions, chosenCompletion, expectedCompletedLine);
|
||||
}
|
||||
|
||||
private void assertCompletions(String completionLine, int noOfExcpectedCompletions, String[] expectedCompletions, int chosenCompletion, String expectedCompletedLine) throws Exception {
|
||||
String editorContent = """
|
||||
package org.test;
|
||||
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
|
||||
public class CronScheduler {
|
||||
|
||||
""" +
|
||||
completionLine + "\n" +
|
||||
"""
|
||||
public void cronCompletionTest() {
|
||||
}
|
||||
""";
|
||||
|
||||
Editor editor = harness.newEditor(LanguageId.JAVA, editorContent, tempJavaDocUri);
|
||||
|
||||
List<CompletionItem> completions = editor.getCompletions();
|
||||
assertEquals(noOfExcpectedCompletions, completions.size());
|
||||
|
||||
if (expectedCompletions != null) {
|
||||
String[] completionItems = completions.stream()
|
||||
.map(item -> item.getLabel())
|
||||
.toArray(size -> new String[size]);
|
||||
|
||||
assertArrayEquals(expectedCompletions, completionItems);
|
||||
}
|
||||
|
||||
if (noOfExcpectedCompletions > 0) {
|
||||
editor.apply(completions.get(chosenCompletion));
|
||||
assertEquals("""
|
||||
package org.test;
|
||||
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
|
||||
public class CronScheduler {
|
||||
|
||||
""" + expectedCompletedLine + "\n" +
|
||||
"""
|
||||
public void cronCompletionTest() {
|
||||
}
|
||||
""", editor.getText());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 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.cron;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.lsp4j.InlayHint;
|
||||
import org.eclipse.lsp4j.Position;
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
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.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.util.text.LanguageId;
|
||||
import org.springframework.ide.vscode.languageserver.testharness.TextDocumentInfo;
|
||||
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 Udayani V
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@BootLanguageServerTest
|
||||
@Import(SymbolProviderTestConf.class)
|
||||
public class CronExpressionsInlayHintsProviderTest {
|
||||
|
||||
@Autowired
|
||||
private BootLanguageServerHarness harness;
|
||||
@Autowired
|
||||
private JavaProjectFinder projectFinder;
|
||||
@Autowired
|
||||
private SpringSymbolIndex indexer;
|
||||
private SimpleLanguageServer server;
|
||||
private File directory;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() throws Exception {
|
||||
harness.intialize(null);
|
||||
directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotations/").toURI());
|
||||
String projectDir = directory.toURI().toString();
|
||||
server = mock(SimpleLanguageServer.class);
|
||||
|
||||
// trigger project creation
|
||||
projectFinder.find(new TextDocumentIdentifier(projectDir)).get();
|
||||
|
||||
CompletableFuture<Void> initProject = indexer.waitOperation();
|
||||
initProject.get(5, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_cronExpresionInlayHints() throws Exception {
|
||||
|
||||
String docUri = directory.toPath().resolve("src/main/java/org/test/CronScheduler.java").toUri().toString();
|
||||
TextDocumentInfo doc = harness.getOrReadFile(new File(new URI(docUri)), LanguageId.JAVA.getId());
|
||||
TextDocumentInfo openedDoc = harness.openDocument(doc);
|
||||
|
||||
List<InlayHint> inlayHints = harness.getInlayHints(openedDoc);
|
||||
|
||||
assertEquals(9, inlayHints.size());
|
||||
|
||||
assertTrue(containsInlayHints(inlayHints.get(0), 10, 33, "every hour"));
|
||||
assertTrue(containsInlayHints(inlayHints.get(1), 15, 33, "every minute at second 01"));
|
||||
assertTrue(containsInlayHints(inlayHints.get(2), 20, 34, "every minute at second 10"));
|
||||
assertTrue(containsInlayHints(inlayHints.get(3), 25, 36, "every hour between 8 and 10"));
|
||||
assertTrue(containsInlayHints(inlayHints.get(4), 30, 36, "at 6 and 19 hours"));
|
||||
assertTrue(containsInlayHints(inlayHints.get(5), 35, 39, "every 30 minutes every hour between 8 and 10"));
|
||||
assertTrue(containsInlayHints(inlayHints.get(6), 40, 42,
|
||||
"every hour between 9 and 17 every day between Monday and Friday"));
|
||||
assertTrue(containsInlayHints(inlayHints.get(7), 45, 35, "every hour every day between Monday and Friday"));
|
||||
assertTrue(
|
||||
containsInlayHints(inlayHints.get(8), 51, 9, "every second between 0 and 59 at 10 minute at 13 hour"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_noInlayHintsForInvalidCronExp() throws Exception {
|
||||
|
||||
String docUri = directory.toPath().resolve("src/main/java/org/test/Scheduler.java").toUri().toString();
|
||||
TextDocumentInfo doc = harness.getOrReadFile(new File(new URI(docUri)), LanguageId.JAVA.getId());
|
||||
TextDocumentInfo openedDoc = harness.openDocument(doc);
|
||||
|
||||
List<InlayHint> inlayHints = harness.getInlayHints(openedDoc);
|
||||
|
||||
assertEquals(0, inlayHints.size());
|
||||
}
|
||||
|
||||
private boolean containsInlayHints(InlayHint inlayHint, int line, int character, String label) {
|
||||
Position pos = inlayHint.getPosition();
|
||||
String inlayText = inlayHint.getLabel().getLeft();
|
||||
if (pos.getLine() == line && pos.getCharacter() == character && inlayText.equals(label)
|
||||
&& inlayText.equals(label)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -294,6 +294,20 @@ public class ValueCompletionTest {
|
||||
assertClasspathCompletions();
|
||||
}
|
||||
|
||||
// The parser removes the (spring.) piece from the AST in this case, so there is no way
|
||||
// to clearly identify this case
|
||||
//
|
||||
// @Test
|
||||
// void testComplexPrefixCompletionParamNameCursorRightAfterDot() throws Exception {
|
||||
// prepareCase("@Value(\"onField\")", "@Value(spring.<*>)");
|
||||
// prepareDefaultIndexData();
|
||||
//
|
||||
// assertPropertyCompletions(
|
||||
// "@Value(\"${spring.prop1}\"<*>)");
|
||||
//
|
||||
// assertClasspathCompletions();
|
||||
// }
|
||||
|
||||
@Test
|
||||
void testComplexPrefixCompletion() throws Exception {
|
||||
prepareCase("@Value(\"onField\")", "@Value(spring.pr<*>)");
|
||||
@@ -316,6 +330,20 @@ public class ValueCompletionTest {
|
||||
assertClasspathCompletions();
|
||||
}
|
||||
|
||||
// The parser removes the (spring.) piece from the AST in this case, so there is no way
|
||||
// to clearly identify this case
|
||||
//
|
||||
// @Test
|
||||
// void testPrefixCompletionWithParamNameCursorRightAfterDot() throws Exception {
|
||||
// prepareCase("@Value(\"onField\")", "@Value(value=spring.<*>)");
|
||||
// prepareDefaultIndexData();
|
||||
//
|
||||
// assertPropertyCompletions(
|
||||
// "@Value(value=\"${spring.prop1}\"<*>)");
|
||||
//
|
||||
// assertClasspathCompletions();
|
||||
// }
|
||||
|
||||
@Test
|
||||
void testComplexPrefixCompletionWithParamName() throws Exception {
|
||||
prepareCase("@Value(\"onField\")", "@Value(value=spring.pr<*>)");
|
||||
@@ -419,6 +447,17 @@ public class ValueCompletionTest {
|
||||
assertClasspathCompletions();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testComplexPrefixCompletionWithQuotesAndDotRightAfterPrefix() throws Exception {
|
||||
prepareCase("@Value(\"onField\")", "@Value(\"spring.<*>\")");
|
||||
prepareDefaultIndexData();
|
||||
|
||||
assertPropertyCompletions(
|
||||
"@Value(\"${spring.prop1}<*>\")");
|
||||
|
||||
assertClasspathCompletions();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testComplexPrefixCompletionWithQuotes() throws Exception {
|
||||
prepareCase("@Value(\"onField\")", "@Value(\"spring.pr<*>\")");
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package org.test;
|
||||
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
|
||||
public class CronScheduler {
|
||||
|
||||
@Scheduled(cron = "")
|
||||
public void cronCompletionTest() {
|
||||
}
|
||||
|
||||
@Scheduled(cron = "0 0 * * * *")
|
||||
public void performTask1() {
|
||||
System.out.println("Scheduled task executed");
|
||||
}
|
||||
|
||||
@Scheduled(cron = "1 * * * * *")
|
||||
public void performTask2() {
|
||||
System.out.println("Scheduled task executed");
|
||||
}
|
||||
|
||||
@Scheduled(cron = "10 * * * * *")
|
||||
public void performTask3() {
|
||||
System.out.println("Scheduled task executed");
|
||||
}
|
||||
|
||||
@Scheduled(cron = "0 0 8-10 * * *")
|
||||
public void performTask4() {
|
||||
System.out.println("Scheduled task executed");
|
||||
}
|
||||
|
||||
@Scheduled(cron = "0 0 6,19 * * *")
|
||||
public void performTask5() {
|
||||
System.out.println("Scheduled task executed");
|
||||
}
|
||||
|
||||
@Scheduled(cron = "0 0/30 8-10 * * *")
|
||||
public void performTask6() {
|
||||
System.out.println("Scheduled task executed");
|
||||
}
|
||||
|
||||
@Scheduled(cron = "0 0 9-17 * * MON-FRI")
|
||||
public void performTask7() {
|
||||
System.out.println("Scheduled task executed");
|
||||
}
|
||||
|
||||
@Scheduled(cron = "0 0 * * * 1-5")
|
||||
public void performTask8() {
|
||||
System.out.println("Scheduled task executed");
|
||||
}
|
||||
|
||||
@Scheduled(cron = "0-59 10 13 * * *", zone = "UTC", fixedRate = 5000, initialDelay
|
||||
= 1000)
|
||||
public void performTask9() {
|
||||
System.out.println("Scheduled task executed");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.test;
|
||||
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
|
||||
public class Scheduler {
|
||||
|
||||
@Scheduled(cron = "MON 10 13 * * *")
|
||||
public void invalidExp() {
|
||||
System.out.println("Scheduled task executed");
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user