JPQL syntax highlighting via Semantic Tokens
This commit is contained in:
@@ -101,6 +101,11 @@ public class BootJavaConfig implements InitializingBean {
|
||||
return isAll != null && isAll.booleanValue();
|
||||
}
|
||||
|
||||
public boolean isJpqlEnabled() {
|
||||
Boolean isEnabled = settings.getBoolean("boot-java", "jpql");
|
||||
return isEnabled != null && isEnabled.booleanValue();
|
||||
}
|
||||
|
||||
public String[] xmlBeansFoldersToScan() {
|
||||
String foldersStr = settings.getString("boot-java", "support-spring-xml-config", "scan-folders");
|
||||
if (foldersStr != null) {
|
||||
|
||||
@@ -361,6 +361,9 @@ public class BootLanguageServerBootApp {
|
||||
String fileName = path.getFileName().toString();
|
||||
switch (Files.getFileExtension(fileName)) {
|
||||
case "properties":
|
||||
if (path.endsWith("/META-INF/jpa-named-queries.properties")) {
|
||||
return LanguageId.JPA_QUERY_PROPERTIES;
|
||||
}
|
||||
return LanguageId.BOOT_PROPERTIES;
|
||||
case "yml":
|
||||
return LanguageId.BOOT_PROPERTIES_YAML;
|
||||
|
||||
@@ -25,6 +25,9 @@ import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.ide.vscode.boot.factories.SpringFactoriesLanguageServerComponents;
|
||||
import org.springframework.ide.vscode.boot.index.cache.IndexCache;
|
||||
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServerComponents;
|
||||
import org.springframework.ide.vscode.boot.java.data.jpa.queries.JpaQueryPropertiesLanguageServerComponents;
|
||||
import org.springframework.ide.vscode.boot.java.data.jpa.queries.JpqlSemanticTokens;
|
||||
import org.springframework.ide.vscode.boot.java.data.jpa.queries.JpqlSupportState;
|
||||
import org.springframework.ide.vscode.boot.java.links.JavaElementLocationProvider;
|
||||
import org.springframework.ide.vscode.boot.java.links.SourceLinks;
|
||||
import org.springframework.ide.vscode.boot.java.livehover.v2.SpringProcessLiveDataProvider;
|
||||
@@ -122,7 +125,8 @@ public class BootLanguageServerInitializer implements InitializingBean {
|
||||
new BootJavaLanguageServerComponents(appContext),
|
||||
new SpringXMLLanguageServerComponents(server, springIndexer, params, config),
|
||||
new SpringFactoriesLanguageServerComponents(projectFinder, springIndexer, config),
|
||||
new PomLanguageServerComponents(server, projectFinder, params.projectObserver, appContext.getBean(SpringProjectsProvider.class))
|
||||
new PomLanguageServerComponents(server, projectFinder, params.projectObserver, appContext.getBean(SpringProjectsProvider.class)),
|
||||
new JpaQueryPropertiesLanguageServerComponents(server.getTextDocumentService(), projectFinder, appContext.getBean(JpqlSemanticTokens.class), appContext.getBean(JpqlSupportState.class))
|
||||
);
|
||||
|
||||
for (LanguageServerComponents c : componentsList) {
|
||||
@@ -152,6 +156,8 @@ public class BootLanguageServerInitializer implements InitializingBean {
|
||||
components.getDocumentSymbolProvider().ifPresent(documents::onDocumentSymbol);
|
||||
|
||||
components.getInlayHintHandler().ifPresent(documents::onInlayHint);
|
||||
|
||||
components.getSemanticTokensHandler().ifPresent(documents::onSemanticTokens);
|
||||
|
||||
startListeningToPerformReconcile();
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2023 VMware, Inc.
|
||||
* Copyright (c) 2023, 2024 VMware, 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
|
||||
@@ -12,6 +12,9 @@ package org.springframework.ide.vscode.boot.app;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.ide.vscode.boot.java.data.jpa.queries.JdtDataQuerySemanticTokensProvider;
|
||||
import org.springframework.ide.vscode.boot.java.data.jpa.queries.JpqlSemanticTokens;
|
||||
import org.springframework.ide.vscode.boot.java.data.jpa.queries.JpqlSupportState;
|
||||
import org.springframework.ide.vscode.boot.java.reconcilers.AddConfigurationIfBeansPresentReconciler;
|
||||
import org.springframework.ide.vscode.boot.java.reconcilers.AnnotationNodeReconciler;
|
||||
import org.springframework.ide.vscode.boot.java.reconcilers.AuthorizeHttpRequestsReconciler;
|
||||
@@ -106,5 +109,9 @@ public class JdtConfig {
|
||||
@Bean EntityIdForRepoReconciler entityIdForRepoReconciler(SimpleLanguageServer server) {
|
||||
return new EntityIdForRepoReconciler();
|
||||
}
|
||||
|
||||
@Bean JdtDataQuerySemanticTokensProvider jpqlJdtSemanticTokensProvider(JpqlSemanticTokens provider, JpqlSupportState supportState) {
|
||||
return new JdtDataQuerySemanticTokensProvider(provider, supportState);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*******************************************************************************
|
||||
* 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.app;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.ide.vscode.boot.java.data.jpa.queries.JpqlSemanticTokens;
|
||||
import org.springframework.ide.vscode.boot.java.data.jpa.queries.JpqlSupportState;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class SpringDataConfig {
|
||||
|
||||
@Bean
|
||||
JpqlSemanticTokens jpqlSemanticTokens() {
|
||||
return new JpqlSemanticTokens();
|
||||
}
|
||||
|
||||
@Bean
|
||||
JpqlSupportState jpqlSupportState(SimpleLanguageServer server, ProjectObserver projectObserver, BootJavaConfig config) {
|
||||
return new JpqlSupportState(server, projectObserver, config);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -62,6 +62,7 @@ import org.springframework.ide.vscode.commons.languageserver.composable.Language
|
||||
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.reconcile.IReconcileEngine;
|
||||
import org.springframework.ide.vscode.commons.languageserver.semantic.tokens.SemanticTokensHandler;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.CodeActionHandler;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.CodeLensHandler;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.DocumentHighlightHandler;
|
||||
@@ -111,6 +112,7 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
|
||||
private BootJavaReconcileEngine reconcileEngine;
|
||||
private BootJavaCodeActionProvider codeActionProvider;
|
||||
private DocumentSymbolHandler docSymbolProvider;
|
||||
private JdtSemanticTokensHandler semanticTokensHandler;
|
||||
|
||||
private SpringProcessTracker liveProcessTracker;
|
||||
|
||||
@@ -183,6 +185,11 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
|
||||
|
||||
codeActionProvider = appContext.getBean(BootJavaCodeActionProvider.class);
|
||||
|
||||
Map<String, JdtSemanticTokensProvider> jdtSemanticTokensProviders = appContext.getBeansOfType(JdtSemanticTokensProvider.class);
|
||||
if (!jdtSemanticTokensProviders.isEmpty()) {
|
||||
semanticTokensHandler = new JdtSemanticTokensHandler(cuCache, projectFinder, jdtSemanticTokensProviders.values());
|
||||
}
|
||||
|
||||
config.addListener(ignore -> {
|
||||
log.info("update live process tracker settings - start");
|
||||
|
||||
@@ -353,5 +360,10 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
|
||||
return Optional.ofNullable(codeActionProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<SemanticTokensHandler> getSemanticTokensHandler() {
|
||||
return Optional.ofNullable(semanticTokensHandler);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
/*******************************************************************************
|
||||
* 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;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.eclipse.lsp4j.DocumentFilter;
|
||||
import org.eclipse.lsp4j.SemanticTokens;
|
||||
import org.eclipse.lsp4j.SemanticTokensLegend;
|
||||
import org.eclipse.lsp4j.SemanticTokensParams;
|
||||
import org.eclipse.lsp4j.SemanticTokensWithRegistrationOptions;
|
||||
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
|
||||
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.languageserver.semantic.tokens.SemanticTokenData;
|
||||
import org.springframework.ide.vscode.commons.languageserver.semantic.tokens.SemanticTokensHandler;
|
||||
import org.springframework.ide.vscode.commons.languageserver.semantic.tokens.SemanticTokensUtils;
|
||||
import org.springframework.ide.vscode.commons.util.text.LanguageId;
|
||||
|
||||
public class JdtSemanticTokensHandler implements SemanticTokensHandler {
|
||||
|
||||
private final CompilationUnitCache cuCache;
|
||||
private final JavaProjectFinder projectFinder;
|
||||
private final Collection<JdtSemanticTokensProvider> tokenProviders;
|
||||
private final SemanticTokensLegend legend;
|
||||
|
||||
public JdtSemanticTokensHandler(CompilationUnitCache cuCache, JavaProjectFinder projectFinder, Collection<JdtSemanticTokensProvider> tokenProviders) {
|
||||
this.cuCache = cuCache;
|
||||
this.projectFinder = projectFinder;
|
||||
this.tokenProviders = tokenProviders;
|
||||
this.legend = new SemanticTokensLegend(
|
||||
tokenProviders.stream().flatMap(tp -> tp.getTokenTypes().stream()).distinct().collect(Collectors.toList()),
|
||||
tokenProviders.stream().flatMap(tp -> tp.getTokenModifiers().stream()).distinct().collect(Collectors.toList())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SemanticTokensWithRegistrationOptions getCapability() {
|
||||
SemanticTokensWithRegistrationOptions capabilities = new SemanticTokensWithRegistrationOptions();
|
||||
DocumentFilter documentFilter = new DocumentFilter();
|
||||
documentFilter.setLanguage(LanguageId.JAVA.getId());
|
||||
capabilities.setDocumentSelector(List.of(documentFilter));
|
||||
capabilities.setFull(true);
|
||||
capabilities.setLegend(legend);
|
||||
return capabilities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SemanticTokens semanticTokensFull(SemanticTokensParams params, CancelChecker cancelChecker) {
|
||||
Optional<IJavaProject> optProject = projectFinder.find(params.getTextDocument());
|
||||
if (optProject.isPresent()) {
|
||||
IJavaProject jp = optProject.get();
|
||||
List<JdtSemanticTokensProvider> applicableTokenProviders = tokenProviders.stream().filter(tp -> tp.isApplicable(jp)).collect(Collectors.toList());
|
||||
if (!applicableTokenProviders.isEmpty()) {
|
||||
return cuCache.withCompilationUnit(jp, URI.create(params.getTextDocument().getUri()), cu -> computeTokens(applicableTokenProviders, cu));
|
||||
}
|
||||
}
|
||||
return new SemanticTokens();
|
||||
}
|
||||
|
||||
private SemanticTokens computeTokens(List<JdtSemanticTokensProvider> applicableTokenProviders, CompilationUnit cu) {
|
||||
List<SemanticTokenData> tokensData = applicableTokenProviders.stream().map(tp -> tp.computeTokens(cu)).flatMap(t -> t.stream()).collect(Collectors.toList());
|
||||
return new SemanticTokens(SemanticTokensUtils.mapTokensDataToLsp(tokensData, legend, offset -> cu.getLineNumber(offset) - 1, cu::getColumnNumber));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*******************************************************************************
|
||||
* 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;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.semantic.tokens.SemanticTokenData;
|
||||
|
||||
public interface JdtSemanticTokensProvider {
|
||||
|
||||
List<String> getTokenTypes();
|
||||
default List<String> getTokenModifiers() { return Collections.emptyList(); }
|
||||
List<SemanticTokenData> computeTokens(CompilationUnit cu);
|
||||
boolean isApplicable(IJavaProject project);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/*******************************************************************************
|
||||
* 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.data.jpa.queries;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
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.IAnnotationBinding;
|
||||
import org.eclipse.jdt.core.dom.IMemberValuePairBinding;
|
||||
import org.eclipse.jdt.core.dom.IMethodBinding;
|
||||
import org.eclipse.jdt.core.dom.MemberValuePair;
|
||||
import org.eclipse.jdt.core.dom.MethodInvocation;
|
||||
import org.eclipse.jdt.core.dom.NormalAnnotation;
|
||||
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
|
||||
import org.eclipse.jdt.core.dom.StringLiteral;
|
||||
import org.springframework.ide.vscode.boot.java.JdtSemanticTokensProvider;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.java.SpringProjectUtil;
|
||||
import org.springframework.ide.vscode.commons.languageserver.semantic.tokens.SemanticTokenData;
|
||||
|
||||
public class JdtDataQuerySemanticTokensProvider implements JdtSemanticTokensProvider {
|
||||
|
||||
private static final String QUERY = "Query";
|
||||
private static final String FQN_QUERY = "org.springframework.data.jpa.repository." + QUERY;
|
||||
|
||||
private final JpqlSemanticTokens provider;
|
||||
private final JpqlSupportState supportState;
|
||||
|
||||
public JdtDataQuerySemanticTokensProvider(JpqlSemanticTokens provider, JpqlSupportState supportState) {
|
||||
this.provider = provider;
|
||||
this.supportState = supportState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getTokenTypes() {
|
||||
return provider.getTokenTypes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SemanticTokenData> computeTokens(CompilationUnit cu) {
|
||||
List<SemanticTokenData> tokensData = new ArrayList<>();
|
||||
cu.accept(new ASTVisitor() {
|
||||
|
||||
@Override
|
||||
public boolean visit(NormalAnnotation a) {
|
||||
if (isQueryAnnotation(a)) {
|
||||
Expression queryValueNode = ((List<?>) a.values()).stream()
|
||||
.filter(MemberValuePair.class::isInstance)
|
||||
.map(MemberValuePair.class::cast)
|
||||
.filter(p -> "value".equals(p.getName().getIdentifier()))
|
||||
.findFirst().map(p -> p.getValue())
|
||||
.get();
|
||||
if (queryValueNode instanceof StringLiteral) {
|
||||
IAnnotationBinding annotationBinding = a.resolveAnnotationBinding();
|
||||
String query = getJpaQuery(annotationBinding);
|
||||
if (query != null && !query.isBlank()) {
|
||||
int valueOffset = queryValueNode.getStartPosition() + 1;
|
||||
tokensData.addAll(provider.computeTokens(query, valueOffset));
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(SingleMemberAnnotation a) {
|
||||
if (isQueryAnnotation(a) && a.getValue() instanceof StringLiteral) {
|
||||
IAnnotationBinding annotationBinding = a.resolveAnnotationBinding();
|
||||
String query = getJpaQuery(annotationBinding);
|
||||
if (query != null && !query.isBlank()) {
|
||||
int valueOffset = a.getValue().getStartPosition() + 1;
|
||||
tokensData.addAll(provider.computeTokens(query, valueOffset));
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(MethodInvocation node) {
|
||||
if ("createQuery".equals(node.getName().getIdentifier()) && node.arguments().size() <= 2 && node.arguments().get(0) instanceof StringLiteral queryExpr) {
|
||||
IMethodBinding methodBinding = node.resolveMethodBinding();
|
||||
if ("jakarta.persistence.EntityManager".equals(methodBinding.getDeclaringClass().getQualifiedName())) {
|
||||
if (methodBinding.getParameterTypes().length <= 2 && "java.lang.String".equals(methodBinding.getParameterTypes()[0].getQualifiedName())) {
|
||||
tokensData.addAll(provider.computeTokens(queryExpr.getLiteralValue(), queryExpr.getStartPosition() + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.visit(node);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return tokensData;
|
||||
}
|
||||
|
||||
private static boolean isQueryAnnotation(Annotation a) {
|
||||
return FQN_QUERY.equals(a.getTypeName().getFullyQualifiedName())
|
||||
|| QUERY.equals(a.getTypeName().getFullyQualifiedName());
|
||||
}
|
||||
|
||||
private static String getJpaQuery(IAnnotationBinding annotationBinding) {
|
||||
if (annotationBinding != null && annotationBinding.getAnnotationType() != null) {
|
||||
if (FQN_QUERY.equals(annotationBinding.getAnnotationType().getQualifiedName())) {
|
||||
String query = null;
|
||||
boolean isNative = false;
|
||||
for (IMemberValuePairBinding pair : annotationBinding.getAllMemberValuePairs()) {
|
||||
switch (pair.getName()) {
|
||||
case "value":
|
||||
query = (String) pair.getValue();
|
||||
break;
|
||||
case "nativeQuery":
|
||||
isNative = (Boolean) pair.getValue();
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
return isNative ? null : query;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(IJavaProject project) {
|
||||
return supportState.isEnabled() && SpringProjectUtil.hasDependencyStartingWith(project, "spring-data-jpa", null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*******************************************************************************
|
||||
* 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.data.jpa.queries;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.ide.vscode.commons.languageserver.composable.LanguageServerComponents;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.languageserver.semantic.tokens.SemanticTokensHandler;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
|
||||
import org.springframework.ide.vscode.commons.util.text.LanguageId;
|
||||
|
||||
public class JpaQueryPropertiesLanguageServerComponents implements LanguageServerComponents {
|
||||
|
||||
private final QueryPropertiesSemanticTokensHandler semanticTokensHandler;
|
||||
|
||||
public JpaQueryPropertiesLanguageServerComponents(SimpleTextDocumentService documents, JavaProjectFinder projectsFinder, JpqlSemanticTokens jpqlSemanticTokensProvider, JpqlSupportState supportState) {
|
||||
this.semanticTokensHandler = new QueryPropertiesSemanticTokensHandler(documents, projectsFinder, jpqlSemanticTokensProvider, supportState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<LanguageId> getInterestingLanguages() {
|
||||
return Set.of(LanguageId.JPA_QUERY_PROPERTIES);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<SemanticTokensHandler> getSemanticTokensHandler() {
|
||||
return Optional.of(semanticTokensHandler);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
/*******************************************************************************
|
||||
* 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.data.jpa.queries;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.CommonTokenStream;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.antlr.v4.runtime.tree.ErrorNode;
|
||||
import org.antlr.v4.runtime.tree.TerminalNode;
|
||||
import org.springframework.ide.vscode.commons.languageserver.semantic.tokens.SemanticTokenData;
|
||||
import org.springframework.ide.vscode.commons.languageserver.semantic.tokens.SemanticTokensDataProvider;
|
||||
import org.springframework.ide.vscode.jpql.antlr.parser.JpqlBaseListener;
|
||||
import org.springframework.ide.vscode.jpql.antlr.parser.JpqlLexer;
|
||||
import org.springframework.ide.vscode.jpql.antlr.parser.JpqlParser;
|
||||
import org.springframework.ide.vscode.jpql.antlr.parser.JpqlParser.Collection_valued_fieldContext;
|
||||
import org.springframework.ide.vscode.jpql.antlr.parser.JpqlParser.Entity_nameContext;
|
||||
import org.springframework.ide.vscode.jpql.antlr.parser.JpqlParser.Entity_type_literalContext;
|
||||
import org.springframework.ide.vscode.jpql.antlr.parser.JpqlParser.Single_valued_object_fieldContext;
|
||||
import org.springframework.ide.vscode.jpql.antlr.parser.JpqlParser.State_fieldContext;
|
||||
|
||||
public class JpqlSemanticTokens implements SemanticTokensDataProvider {
|
||||
|
||||
private static List<String> TOKEN_TYPES = List.of("keyword", "type", "class", "string", "number", "operator",
|
||||
"variable", "method", "modifier", "regexp");
|
||||
|
||||
@Override
|
||||
public List<String> getTokenTypes() {
|
||||
return TOKEN_TYPES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SemanticTokenData> computeTokens(String text, int initialOffset) {
|
||||
List<SemanticTokenData> tokens = new ArrayList<>();
|
||||
JpqlLexer lexer = new JpqlLexer(CharStreams.fromString(text));
|
||||
CommonTokenStream antlrTokens = new CommonTokenStream(lexer);
|
||||
JpqlParser parser = new JpqlParser(antlrTokens);
|
||||
HashSet<Token> coloredTokens = new HashSet<>();
|
||||
parser.addParseListener(new JpqlBaseListener() {
|
||||
|
||||
private void addToken(Token token, String tokenType) {
|
||||
tokens.add(new SemanticTokenData(token.getStartIndex() + initialOffset,
|
||||
token.getStartIndex() + token.getText().length() + initialOffset, tokenType, new String[0]));
|
||||
coloredTokens.add(token);
|
||||
}
|
||||
|
||||
private void processTerminalNode(TerminalNode node) {
|
||||
if (coloredTokens.contains(node.getSymbol())) {
|
||||
return;
|
||||
}
|
||||
int type = node.getSymbol().getType();
|
||||
switch (type) {
|
||||
case JpqlParser.STRINGLITERAL:
|
||||
case JpqlParser.CHARACTER:
|
||||
addToken(node.getSymbol(), "string");
|
||||
break;
|
||||
case JpqlParser.LONGLITERAL:
|
||||
case JpqlParser.INTLITERAL:
|
||||
case JpqlParser.FLOATLITERAL:
|
||||
addToken(node.getSymbol(), "number");
|
||||
break;
|
||||
case JpqlParser.IDENTIFICATION_VARIABLE:
|
||||
addToken(node.getSymbol(), "variable");
|
||||
break;
|
||||
case JpqlParser.JAVASTRINGLITERAL:
|
||||
addToken(node.getSymbol(), "class");
|
||||
break;
|
||||
case JpqlParser.EQUAL:
|
||||
case JpqlParser.NOT_EQUAL:
|
||||
addToken(node.getSymbol(), "operator");
|
||||
break;
|
||||
case JpqlParser.WS:
|
||||
break;
|
||||
case JpqlParser.SPEL:
|
||||
addToken(node.getSymbol(), "regexp");
|
||||
break;
|
||||
default:
|
||||
if (JpqlParser.WS < type && type <= JpqlParser.WHERE) {
|
||||
addToken(node.getSymbol(), "keyword");
|
||||
} else {
|
||||
addToken(node.getSymbol(), "modifier");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enterState_field(State_fieldContext ctx) {
|
||||
if (!coloredTokens.contains(ctx.getStart())) {
|
||||
addToken(ctx.getStart(), "method");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enterSingle_valued_object_field(Single_valued_object_fieldContext ctx) {
|
||||
if (!coloredTokens.contains(ctx.getStart())) {
|
||||
addToken(ctx.getStart(), "method");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enterCollection_valued_field(Collection_valued_fieldContext ctx) {
|
||||
if (!coloredTokens.contains(ctx.getStart())) {
|
||||
addToken(ctx.getStart(), "method");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enterEntity_name(Entity_nameContext ctx) {
|
||||
if (!coloredTokens.contains(ctx.getStart())) {
|
||||
addToken(ctx.getStart(), "class");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enterEntity_type_literal(Entity_type_literalContext ctx) {
|
||||
if (!coloredTokens.contains(ctx.getStart())) {
|
||||
addToken(ctx.getStart(), "type");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitTerminal(TerminalNode node) {
|
||||
processTerminalNode(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitErrorNode(ErrorNode node) {
|
||||
processTerminalNode(node);
|
||||
}
|
||||
|
||||
});
|
||||
parser.ql_statement();
|
||||
return tokens;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*******************************************************************************
|
||||
* 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.data.jpa.queries;
|
||||
|
||||
import org.springframework.ide.vscode.boot.app.BootJavaConfig;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
|
||||
public final class JpqlSupportState {
|
||||
|
||||
private final SimpleLanguageServer server;
|
||||
private boolean enabled;
|
||||
|
||||
public JpqlSupportState(SimpleLanguageServer server, ProjectObserver projectObserver, BootJavaConfig config) {
|
||||
this(server, projectObserver, config, config.isJpqlEnabled());
|
||||
}
|
||||
|
||||
public JpqlSupportState(SimpleLanguageServer server, ProjectObserver projectObserver, BootJavaConfig config, boolean enabled) {
|
||||
this.server = server;
|
||||
this.enabled = enabled;
|
||||
config.addListener(v -> setEnabled(config.isJpqlEnabled()));
|
||||
projectObserver.addListener(ProjectObserver.onAny(jp -> server.getAsync().execute(() -> server.getClient().refreshSemanticTokens())));
|
||||
}
|
||||
|
||||
public synchronized boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
private synchronized void setEnabled(boolean enabled) {
|
||||
if (this.enabled != enabled) {
|
||||
this.enabled = enabled;
|
||||
server.getAsync().execute(() -> server.getClient().refreshSemanticTokens());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*******************************************************************************
|
||||
* 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.data.jpa.queries;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.lsp4j.DocumentFilter;
|
||||
import org.eclipse.lsp4j.SemanticTokens;
|
||||
import org.eclipse.lsp4j.SemanticTokensLegend;
|
||||
import org.eclipse.lsp4j.SemanticTokensParams;
|
||||
import org.eclipse.lsp4j.SemanticTokensWithRegistrationOptions;
|
||||
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.java.SpringProjectUtil;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.languageserver.semantic.tokens.SemanticTokenData;
|
||||
import org.springframework.ide.vscode.commons.languageserver.semantic.tokens.SemanticTokensHandler;
|
||||
import org.springframework.ide.vscode.commons.languageserver.semantic.tokens.SemanticTokensUtils;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
|
||||
import org.springframework.ide.vscode.commons.util.BadLocationException;
|
||||
import org.springframework.ide.vscode.commons.util.text.LanguageId;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
import org.springframework.ide.vscode.java.properties.antlr.parser.AntlrParser;
|
||||
import org.springframework.ide.vscode.java.properties.parser.ParseResults;
|
||||
import org.springframework.ide.vscode.java.properties.parser.PropertiesAst;
|
||||
import org.springframework.ide.vscode.java.properties.parser.PropertiesAst.Value;
|
||||
|
||||
public class QueryPropertiesSemanticTokensHandler implements SemanticTokensHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(QueryPropertiesSemanticTokensHandler.class);
|
||||
|
||||
private final JavaProjectFinder projectFinder;
|
||||
private final SimpleTextDocumentService documents;
|
||||
private final JpqlSemanticTokens tokensProvider;
|
||||
private final JpqlSupportState supportState;
|
||||
|
||||
public QueryPropertiesSemanticTokensHandler(SimpleTextDocumentService documents, JavaProjectFinder projectFinder, JpqlSemanticTokens jpqlTokensProvider, JpqlSupportState supportState) {
|
||||
this.documents = documents;
|
||||
this.projectFinder = projectFinder;
|
||||
this.tokensProvider = jpqlTokensProvider;
|
||||
this.supportState = supportState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SemanticTokensWithRegistrationOptions getCapability() {
|
||||
SemanticTokensWithRegistrationOptions capabilities = new SemanticTokensWithRegistrationOptions();
|
||||
DocumentFilter documentFilter = new DocumentFilter();
|
||||
documentFilter.setLanguage(LanguageId.JPA_QUERY_PROPERTIES.getId());
|
||||
capabilities.setDocumentSelector(List.of(documentFilter));
|
||||
capabilities.setFull(true);
|
||||
capabilities.setLegend(new SemanticTokensLegend(tokensProvider.getTokenTypes(), tokensProvider.getTypeModifiers()));
|
||||
return capabilities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SemanticTokens semanticTokensFull(SemanticTokensParams params, CancelChecker cancelChecker) {
|
||||
if (!supportState.isEnabled()) {
|
||||
return new SemanticTokens();
|
||||
}
|
||||
Optional<IJavaProject> optProject = projectFinder.find(params.getTextDocument());
|
||||
if (optProject.isPresent() && SpringProjectUtil.hasDependencyStartingWith(optProject.get(), "spring-data-jpa", null)) {
|
||||
TextDocument doc = documents.getLatestSnapshot(params.getTextDocument().getUri());
|
||||
if (doc != null) {
|
||||
AntlrParser propertiesParser = new AntlrParser();
|
||||
ParseResults result = propertiesParser.parse(doc.get());
|
||||
List<SemanticTokenData> data = new ArrayList<>();
|
||||
for (PropertiesAst.KeyValuePair node : result.ast.getNodes(PropertiesAst.KeyValuePair.class)) {
|
||||
Value value = node.getValue();
|
||||
if (value != null) {
|
||||
data.addAll(tokensProvider.computeTokens(value.decode(), value.getOffset()));
|
||||
}
|
||||
}
|
||||
Collections.sort(data);
|
||||
SemanticTokensLegend legend = new SemanticTokensLegend(tokensProvider.getTokenTypes(), tokensProvider.getTypeModifiers());
|
||||
return new SemanticTokens(SemanticTokensUtils.mapTokensDataToLsp(data, legend, t -> {
|
||||
try {
|
||||
return doc.getLineOfOffset(t);
|
||||
} catch (BadLocationException e) {
|
||||
log.error("", e);
|
||||
}
|
||||
return -1;
|
||||
}, o -> {
|
||||
try {
|
||||
return o - doc.getLineOffset(doc.getLineOfOffset(o));
|
||||
} catch (BadLocationException e) {
|
||||
log.error("", e);
|
||||
}
|
||||
return -1;
|
||||
}));
|
||||
}
|
||||
}
|
||||
return SemanticTokensHandler.super.semanticTokensFull(params, cancelChecker);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
/*******************************************************************************
|
||||
* 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.data.jpa.queries;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
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.BootLanguageServerTest;
|
||||
import org.springframework.ide.vscode.boot.bootiful.HoverTestConf;
|
||||
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
|
||||
import org.springframework.ide.vscode.commons.languageserver.semantic.tokens.SemanticTokenData;
|
||||
import org.springframework.ide.vscode.commons.maven.java.MavenJavaProject;
|
||||
import org.springframework.ide.vscode.project.harness.ProjectsHarness;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@BootLanguageServerTest
|
||||
@Import(HoverTestConf.class)
|
||||
public class JdtDataQuerySemanticTokensProviderTest {
|
||||
|
||||
@Autowired JdtDataQuerySemanticTokensProvider provider;
|
||||
|
||||
private ProjectsHarness projects = ProjectsHarness.INSTANCE;
|
||||
|
||||
private MavenJavaProject jp;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() throws Exception {
|
||||
jp = projects.mavenProject("spring-modulith-example-full");
|
||||
}
|
||||
|
||||
@Test
|
||||
void singleMemberAnnotation() throws Exception {
|
||||
String source = """
|
||||
package my.package
|
||||
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
|
||||
public interface OwnerRepository {
|
||||
|
||||
@Query("SELECT DISTINCT owner FROM Owner owner")
|
||||
void findByLastName();
|
||||
}
|
||||
""";
|
||||
|
||||
String uri = Paths.get(jp.getLocationUri()).resolve("src/main/resource/my/package/OwnerRepository.java").toUri().toASCIIString();
|
||||
CompilationUnit cu = CompilationUnitCache.parse2(source.toCharArray(), uri, "OwnerRepository.java", jp);
|
||||
|
||||
assertThat(cu).isNotNull();
|
||||
|
||||
List<SemanticTokenData> tokens = provider.computeTokens(cu);
|
||||
|
||||
SemanticTokenData token = tokens.get(0);
|
||||
assertThat(token).isEqualTo(new SemanticTokenData(120, 126, "keyword", new String[0]));
|
||||
assertThat(source.substring(token.start(), token.end())).isEqualTo("SELECT");
|
||||
|
||||
token = tokens.get(1);
|
||||
assertThat(token).isEqualTo(new SemanticTokenData(127, 135, "keyword", new String[0]));
|
||||
assertThat(source.substring(token.start(), token.end())).isEqualTo("DISTINCT");
|
||||
|
||||
token = tokens.get(2);
|
||||
assertThat(token).isEqualTo(new SemanticTokenData(136, 141, "variable", new String[0]));
|
||||
assertThat(source.substring(token.start(), token.end())).isEqualTo("owner");
|
||||
|
||||
token = tokens.get(3);
|
||||
assertThat(token).isEqualTo(new SemanticTokenData(142, 146, "keyword", new String[0]));
|
||||
assertThat(source.substring(token.start(), token.end())).isEqualTo("FROM");
|
||||
|
||||
token = tokens.get(4);
|
||||
assertThat(token).isEqualTo(new SemanticTokenData(147, 152, "class", new String[0]));
|
||||
assertThat(source.substring(token.start(), token.end())).isEqualTo("Owner");
|
||||
|
||||
token = tokens.get(5);
|
||||
assertThat(token).isEqualTo(new SemanticTokenData(153, 158, "variable", new String[0]));
|
||||
assertThat(source.substring(token.start(), token.end())).isEqualTo("owner");
|
||||
}
|
||||
|
||||
@Test
|
||||
void normalAnnotation() throws Exception {
|
||||
String source = """
|
||||
package my.package
|
||||
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
|
||||
public interface OwnerRepository {
|
||||
|
||||
@Query(value = "SELECT DISTINCT owner FROM Owner owner")
|
||||
void findByLastName();
|
||||
}
|
||||
""";
|
||||
|
||||
String uri = Paths.get(jp.getLocationUri()).resolve("src/main/resource/my/package/OwnerRepository.java").toUri().toASCIIString();
|
||||
CompilationUnit cu = CompilationUnitCache.parse2(source.toCharArray(), uri, "OwnerRepository.java", jp);
|
||||
|
||||
assertThat(cu).isNotNull();
|
||||
|
||||
List<SemanticTokenData> tokens = provider.computeTokens(cu);
|
||||
|
||||
SemanticTokenData token = tokens.get(0);
|
||||
assertThat(token).isEqualTo(new SemanticTokenData(128, 134, "keyword", new String[0]));
|
||||
assertThat(source.substring(token.start(), token.end())).isEqualTo("SELECT");
|
||||
|
||||
token = tokens.get(1);
|
||||
assertThat(token).isEqualTo(new SemanticTokenData(135, 143, "keyword", new String[0]));
|
||||
assertThat(source.substring(token.start(), token.end())).isEqualTo("DISTINCT");
|
||||
|
||||
token = tokens.get(2);
|
||||
assertThat(token).isEqualTo(new SemanticTokenData(144, 149, "variable", new String[0]));
|
||||
assertThat(source.substring(token.start(), token.end())).isEqualTo("owner");
|
||||
|
||||
token = tokens.get(3);
|
||||
assertThat(token).isEqualTo(new SemanticTokenData(150, 154, "keyword", new String[0]));
|
||||
assertThat(source.substring(token.start(), token.end())).isEqualTo("FROM");
|
||||
|
||||
token = tokens.get(4);
|
||||
assertThat(token).isEqualTo(new SemanticTokenData(155, 160, "class", new String[0]));
|
||||
assertThat(source.substring(token.start(), token.end())).isEqualTo("Owner");
|
||||
|
||||
token = tokens.get(5);
|
||||
assertThat(token).isEqualTo(new SemanticTokenData(161, 166, "variable", new String[0]));
|
||||
assertThat(source.substring(token.start(), token.end())).isEqualTo("owner");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createQueryMethod() throws Exception {
|
||||
String source = """
|
||||
package my.package
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
|
||||
public interface OwnerRepository {
|
||||
|
||||
default void findByLastName(EntityManager manager) {
|
||||
manager.createQuery("SELECT DISTINCT owner FROM Owner owner")
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
String uri = Paths.get(jp.getLocationUri()).resolve("src/main/resource/my/package/OwnerRepository.java").toUri().toASCIIString();
|
||||
CompilationUnit cu = CompilationUnitCache.parse2(source.toCharArray(), uri, "OwnerRepository.java", jp);
|
||||
|
||||
assertThat(cu).isNotNull();
|
||||
|
||||
List<SemanticTokenData> tokens = provider.computeTokens(cu);
|
||||
|
||||
SemanticTokenData token = tokens.get(0);
|
||||
assertThat(token).isEqualTo(new SemanticTokenData(176, 182, "keyword", new String[0]));
|
||||
assertThat(source.substring(token.start(), token.end())).isEqualTo("SELECT");
|
||||
|
||||
token = tokens.get(1);
|
||||
assertThat(token).isEqualTo(new SemanticTokenData(183, 191, "keyword", new String[0]));
|
||||
assertThat(source.substring(token.start(), token.end())).isEqualTo("DISTINCT");
|
||||
|
||||
token = tokens.get(2);
|
||||
assertThat(token).isEqualTo(new SemanticTokenData(192, 197, "variable", new String[0]));
|
||||
assertThat(source.substring(token.start(), token.end())).isEqualTo("owner");
|
||||
|
||||
token = tokens.get(3);
|
||||
assertThat(token).isEqualTo(new SemanticTokenData(198, 202, "keyword", new String[0]));
|
||||
assertThat(source.substring(token.start(), token.end())).isEqualTo("FROM");
|
||||
|
||||
token = tokens.get(4);
|
||||
assertThat(token).isEqualTo(new SemanticTokenData(203, 208, "class", new String[0]));
|
||||
assertThat(source.substring(token.start(), token.end())).isEqualTo("Owner");
|
||||
|
||||
token = tokens.get(5);
|
||||
assertThat(token).isEqualTo(new SemanticTokenData(209, 214, "variable", new String[0]));
|
||||
assertThat(source.substring(token.start(), token.end())).isEqualTo("owner");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nativeQuery() throws Exception {
|
||||
String source = """
|
||||
package my.package
|
||||
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
|
||||
public interface OwnerRepository {
|
||||
|
||||
@Query(value = "SELECT DISTINCT owner FROM Owner owner", nativeQuery = true)
|
||||
void findByLastName();
|
||||
}
|
||||
""";
|
||||
|
||||
String uri = Paths.get(jp.getLocationUri()).resolve("src/main/resource/my/package/OwnerRepository.java").toUri().toASCIIString();
|
||||
CompilationUnit cu = CompilationUnitCache.parse2(source.toCharArray(), uri, "OwnerRepository.java", jp);
|
||||
|
||||
assertThat(cu).isNotNull();
|
||||
|
||||
List<SemanticTokenData> tokens = provider.computeTokens(cu);
|
||||
|
||||
assertThat(tokens.size()).isZero();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*******************************************************************************
|
||||
* 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.data.jpa.queries;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.ide.vscode.commons.languageserver.semantic.tokens.SemanticTokenData;
|
||||
|
||||
public class JpqlSemanticTokensTest {
|
||||
|
||||
private JpqlSemanticTokens provider = new JpqlSemanticTokens();
|
||||
|
||||
@Test
|
||||
void simpleQuery_1() {
|
||||
List<SemanticTokenData> tokens = provider.computeTokens("SELECT owner FROM Owner owner", 0);
|
||||
assertThat(tokens.get(0)).isEqualTo(new SemanticTokenData(0, 6, "keyword", new String[0]));
|
||||
assertThat(tokens.get(1)).isEqualTo(new SemanticTokenData(7, 12, "variable", new String[0]));
|
||||
assertThat(tokens.get(2)).isEqualTo(new SemanticTokenData(13, 17, "keyword", new String[0]));
|
||||
assertThat(tokens.get(3)).isEqualTo(new SemanticTokenData(18, 23, "class", new String[0]));
|
||||
assertThat(tokens.get(4)).isEqualTo(new SemanticTokenData(24, 29, "variable", new String[0]));
|
||||
|
||||
assertThat(tokens.size()).isEqualTo(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
void initialOffset() {
|
||||
List<SemanticTokenData> tokens = provider.computeTokens("SELECT owner FROM Owner owner", 3);
|
||||
assertThat(tokens.get(0)).isEqualTo(new SemanticTokenData(3, 9, "keyword", new String[0]));
|
||||
assertThat(tokens.get(1)).isEqualTo(new SemanticTokenData(10, 15, "variable", new String[0]));
|
||||
assertThat(tokens.get(2)).isEqualTo(new SemanticTokenData(16, 20, "keyword", new String[0]));
|
||||
assertThat(tokens.get(3)).isEqualTo(new SemanticTokenData(21, 26, "class", new String[0]));
|
||||
assertThat(tokens.get(4)).isEqualTo(new SemanticTokenData(27, 32, "variable", new String[0]));
|
||||
|
||||
assertThat(tokens.size()).isEqualTo(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
void query_with_conflicting_groupby() {
|
||||
List<SemanticTokenData> tokens = provider.computeTokens("SELECT g FROM Group g GROUP BY g.name", 0);
|
||||
assertThat(tokens.get(0)).isEqualTo(new SemanticTokenData(0, 6, "keyword", new String[0]));
|
||||
assertThat(tokens.get(1)).isEqualTo(new SemanticTokenData(7, 8, "variable", new String[0]));
|
||||
assertThat(tokens.get(2)).isEqualTo(new SemanticTokenData(9, 13, "keyword", new String[0]));
|
||||
assertThat(tokens.get(3)).isEqualTo(new SemanticTokenData(14, 19, "class", new String[0]));
|
||||
assertThat(tokens.get(4)).isEqualTo(new SemanticTokenData(20, 21, "variable", new String[0]));
|
||||
assertThat(tokens.get(5)).isEqualTo(new SemanticTokenData(22, 27, "keyword", new String[0]));
|
||||
assertThat(tokens.get(6)).isEqualTo(new SemanticTokenData(28, 30, "keyword", new String[0]));
|
||||
assertThat(tokens.get(7)).isEqualTo(new SemanticTokenData(31, 32, "variable", new String[0]));
|
||||
assertThat(tokens.get(8)).isEqualTo(new SemanticTokenData(32, 33, "modifier", new String[0]));
|
||||
assertThat(tokens.get(9)).isEqualTo(new SemanticTokenData(33, 37, "method", new String[0]));
|
||||
|
||||
assertThat(tokens.size()).isEqualTo(10);
|
||||
}
|
||||
|
||||
@Test
|
||||
void query_with_parameter() {
|
||||
List<SemanticTokenData> tokens = provider.computeTokens("SELECT DISTINCT owner FROM Owner owner left join owner.pets WHERE owner.lastName LIKE :lastName%", 0);
|
||||
assertThat(tokens.get(0)).isEqualTo(new SemanticTokenData(0, 6, "keyword", new String[0])); // SELECT
|
||||
assertThat(tokens.get(1)).isEqualTo(new SemanticTokenData(7, 15, "keyword", new String[0])); // DISTINCT
|
||||
assertThat(tokens.get(2)).isEqualTo(new SemanticTokenData(16, 21, "variable", new String[0])); // owner
|
||||
assertThat(tokens.get(3)).isEqualTo(new SemanticTokenData(22, 26, "keyword", new String[0])); // FROM
|
||||
assertThat(tokens.get(4)).isEqualTo(new SemanticTokenData(27, 32, "class", new String[0])); // Owner
|
||||
assertThat(tokens.get(5)).isEqualTo(new SemanticTokenData(33, 38, "variable", new String[0])); // owner
|
||||
assertThat(tokens.get(6)).isEqualTo(new SemanticTokenData(39, 43, "keyword", new String[0])); // left
|
||||
assertThat(tokens.get(7)).isEqualTo(new SemanticTokenData(44, 48, "keyword", new String[0])); // join
|
||||
assertThat(tokens.get(8)).isEqualTo(new SemanticTokenData(50, 55, "variable", new String[0])); // owner
|
||||
assertThat(tokens.get(9)).isEqualTo(new SemanticTokenData(55, 56, "modifier", new String[0])); // .
|
||||
assertThat(tokens.get(10)).isEqualTo(new SemanticTokenData(56, 60, "method", new String[0])); // pets
|
||||
assertThat(tokens.get(11)).isEqualTo(new SemanticTokenData(61, 66, "keyword", new String[0])); // WHERE
|
||||
assertThat(tokens.get(12)).isEqualTo(new SemanticTokenData(67, 72, "variable", new String[0])); // owner
|
||||
assertThat(tokens.get(13)).isEqualTo(new SemanticTokenData(72, 73, "modifier", new String[0])); // .
|
||||
assertThat(tokens.get(14)).isEqualTo(new SemanticTokenData(73, 81, "method", new String[0])); // lastName
|
||||
assertThat(tokens.get(15)).isEqualTo(new SemanticTokenData(82, 86, "keyword", new String[0])); // LIKE
|
||||
assertThat(tokens.get(16)).isEqualTo(new SemanticTokenData(87, 88, "modifier", new String[0])); // :
|
||||
assertThat(tokens.get(17)).isEqualTo(new SemanticTokenData(88, 96, "variable", new String[0])); // lastName
|
||||
assertThat(tokens.get(18)).isEqualTo(new SemanticTokenData(96, 97, "modifier", new String[0])); // lastName
|
||||
|
||||
assertThat(tokens.size()).isEqualTo(19);
|
||||
}
|
||||
|
||||
@Test
|
||||
void query_with_SPEL() {
|
||||
List<SemanticTokenData> tokens = provider.computeTokens("SELECT owner FROM Owner owner left join fetch owner.pets WHERE owner.id =:${id}", 0);
|
||||
assertThat(tokens.get(0)).isEqualTo(new SemanticTokenData(0, 6, "keyword", new String[0])); // SELECT
|
||||
assertThat(tokens.get(1)).isEqualTo(new SemanticTokenData(7, 12, "variable", new String[0])); // owner
|
||||
assertThat(tokens.get(2)).isEqualTo(new SemanticTokenData(13, 17, "keyword", new String[0])); // FROM
|
||||
assertThat(tokens.get(3)).isEqualTo(new SemanticTokenData(18, 23, "class", new String[0])); // Owner
|
||||
assertThat(tokens.get(4)).isEqualTo(new SemanticTokenData(24, 29, "variable", new String[0])); // owner
|
||||
assertThat(tokens.get(5)).isEqualTo(new SemanticTokenData(30, 34, "keyword", new String[0])); // left
|
||||
assertThat(tokens.get(6)).isEqualTo(new SemanticTokenData(35, 39, "keyword", new String[0])); // join
|
||||
assertThat(tokens.get(7)).isEqualTo(new SemanticTokenData(40, 45, "keyword", new String[0])); // fetch
|
||||
assertThat(tokens.get(8)).isEqualTo(new SemanticTokenData(46, 51, "variable", new String[0])); // owner
|
||||
assertThat(tokens.get(9)).isEqualTo(new SemanticTokenData(51, 52, "modifier", new String[0])); // .
|
||||
assertThat(tokens.get(10)).isEqualTo(new SemanticTokenData(52, 56, "method", new String[0])); // pets
|
||||
assertThat(tokens.get(11)).isEqualTo(new SemanticTokenData(57, 62, "keyword", new String[0])); // WHERE
|
||||
assertThat(tokens.get(12)).isEqualTo(new SemanticTokenData(63, 68, "variable", new String[0])); // owner
|
||||
assertThat(tokens.get(13)).isEqualTo(new SemanticTokenData(68, 69, "modifier", new String[0])); // .
|
||||
assertThat(tokens.get(14)).isEqualTo(new SemanticTokenData(69, 71, "method", new String[0])); // id
|
||||
assertThat(tokens.get(15)).isEqualTo(new SemanticTokenData(72, 73, "operator", new String[0])); // =
|
||||
assertThat(tokens.get(16)).isEqualTo(new SemanticTokenData(73, 74, "modifier", new String[0])); // :
|
||||
assertThat(tokens.get(17)).isEqualTo(new SemanticTokenData(74, 79, "regexp", new String[0])); // ${id}
|
||||
|
||||
assertThat(tokens.size()).isEqualTo(18);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user