Data query parameter go to definition, doc highlights and inlay hints

This commit is contained in:
aboyko
2024-08-23 22:02:24 -04:00
parent 42eca4fed9
commit 00636085ca
34 changed files with 1056 additions and 280 deletions

View File

@@ -31,7 +31,6 @@ import org.eclipse.lsp4j.DocumentSymbolParams;
import org.eclipse.lsp4j.Hover;
import org.eclipse.lsp4j.HoverParams;
import org.eclipse.lsp4j.InlayHint;
import org.eclipse.lsp4j.InlayHintParams;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.SemanticTokensLegend;
import org.eclipse.lsp4j.SemanticTokensWithRegistrationOptions;
@@ -175,15 +174,14 @@ public class CompositeLanguageServerComponents implements LanguageServerComponen
this.inlayHintHandler = new InlayHintHandler() {
@Override
public List<InlayHint> handle(CancelChecker token, InlayHintParams params) {
TextDocument doc = server.getTextDocumentService().getLatestSnapshot(params.getTextDocument().getUri());
public List<InlayHint> handle(TextDocument doc, Range r, CancelChecker token) {
LanguageId language = doc.getLanguageId();
List<LanguageServerComponents> subComponents = componentsByLanguageId.get(language);
if (subComponents != null) {
return subComponents.stream()
.map(sc -> sc.getInlayHintHandler())
.filter(h -> h.isPresent())
.flatMap(h -> h.get().handle(token, params).stream())
.flatMap(h -> h.get().handle(doc, r, token).stream())
.collect(Collectors.toList());
}
// No applicable subEngine...

View File

@@ -13,7 +13,12 @@ package org.springframework.ide.vscode.commons.languageserver.semantic.tokens;
import java.util.Arrays;
import java.util.Objects;
public record SemanticTokenData(int start, int end, String type, String[] modifiers) implements Comparable<SemanticTokenData>{
public record SemanticTokenData(
int start,
int end,
String type,
String[] modifiers
) implements Comparable<SemanticTokenData> {
@Override
public int compareTo(SemanticTokenData o) {

View File

@@ -13,11 +13,12 @@ package org.springframework.ide.vscode.commons.languageserver.util;
import java.util.List;
import org.eclipse.lsp4j.InlayHint;
import org.eclipse.lsp4j.InlayHintParams;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
public interface InlayHintHandler {
List<InlayHint> handle(CancelChecker token, InlayHintParams params);
List<InlayHint> handle(TextDocument doc, Range range, CancelChecker cancelChecker);
}

View File

@@ -581,9 +581,12 @@ public class SimpleTextDocumentService implements TextDocumentService, DocumentE
InlayHintHandler handler = this.inlayHintHandler;
if (handler != null) {
return CompletableFutures.computeAsync(messageWorkerThreadPool, cancelToken -> {
return handler.handle(cancelToken, params);
});
TextDocument doc = getLatestSnapshot(params.getTextDocument().getUri());
if (doc != null) {
return CompletableFutures.computeAsync(messageWorkerThreadPool, cancelToken -> {
return handler.handle(doc, params.getRange(), cancelToken);
});
}
}
return CompletableFuture.completedFuture(Collections.emptyList());
}

View File

@@ -41,6 +41,7 @@ import org.eclipse.lsp4j.CompletionItemTag;
import org.eclipse.lsp4j.CompletionList;
import org.eclipse.lsp4j.DefinitionParams;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.DocumentHighlight;
import org.eclipse.lsp4j.DocumentSymbol;
import org.eclipse.lsp4j.Hover;
import org.eclipse.lsp4j.InsertReplaceEdit;
@@ -240,6 +241,15 @@ public class Editor {
assertEquals(ImmutableList.copyOf(expectedHighlights), actualHighlights);
return ranges;
}
public void assertDocumentHighlights(String afterString, DocumentHighlight... expected) throws Exception {
int pos = getRawText().indexOf(afterString);
if (pos>=0) {
pos += afterString.length();
}
List<? extends DocumentHighlight> actual = harness.getDocumentHighlights(doc.getId(), doc.toPosition(pos));
assertEquals(ImmutableList.copyOf(expected), actual);
}
/**
* Get the editor text, with cursor markers inserted (for easy textual comparison

View File

@@ -35,6 +35,7 @@ import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -65,6 +66,8 @@ import org.eclipse.lsp4j.DidChangeTextDocumentParams;
import org.eclipse.lsp4j.DidChangeWatchedFilesParams;
import org.eclipse.lsp4j.DidCloseTextDocumentParams;
import org.eclipse.lsp4j.DidOpenTextDocumentParams;
import org.eclipse.lsp4j.DocumentHighlight;
import org.eclipse.lsp4j.DocumentHighlightParams;
import org.eclipse.lsp4j.DocumentSymbol;
import org.eclipse.lsp4j.DocumentSymbolCapabilities;
import org.eclipse.lsp4j.DocumentSymbolParams;
@@ -679,6 +682,9 @@ public class LanguageServerHarness {
return getServer().getTextDocumentService().codeLens(params).get();
}
public List<? extends DocumentHighlight> getDocumentHighlights(TextDocumentIdentifier docId, Position cursor) throws InterruptedException, ExecutionException {
return getServer().getTextDocumentService().documentHighlight(new DocumentHighlightParams(docId, cursor)).get();
}
public CompletionItem resolveCompletionItem(CompletionItem maybeUnresolved) {
if (getServer().hasLazyCompletionResolver()) {

View File

@@ -48,6 +48,8 @@ import org.springframework.ide.vscode.boot.java.JavaDefinitionHandler;
import org.springframework.ide.vscode.boot.java.beans.DependsOnDefinitionProvider;
import org.springframework.ide.vscode.boot.java.beans.QualifierDefinitionProvider;
import org.springframework.ide.vscode.boot.java.beans.ResourceDefinitionProvider;
import org.springframework.ide.vscode.boot.java.data.jpa.queries.DataQueryParameterDefinitionProvider;
import org.springframework.ide.vscode.boot.java.data.jpa.queries.JdtDataQuerySemanticTokensProvider;
import org.springframework.ide.vscode.boot.java.handlers.BootJavaCodeActionProvider;
import org.springframework.ide.vscode.boot.java.handlers.BootJavaReconcileEngine;
import org.springframework.ide.vscode.boot.java.handlers.JavaCodeActionHandler;
@@ -394,12 +396,13 @@ public class BootLanguageServerBootApp {
}
@Bean
JavaDefinitionHandler javaDefinitionHandler(CompilationUnitCache cuCache, JavaProjectFinder projectFinder, SpringMetamodelIndex springIndex) {
JavaDefinitionHandler javaDefinitionHandler(SimpleLanguageServer server, CompilationUnitCache cuCache, JavaProjectFinder projectFinder, SpringMetamodelIndex springIndex, JdtDataQuerySemanticTokensProvider qurySemanticTokens) {
return new JavaDefinitionHandler(cuCache, projectFinder, List.of(
new ValueDefinitionProvider(),
new DependsOnDefinitionProvider(springIndex),
new ResourceDefinitionProvider(springIndex),
new QualifierDefinitionProvider(springIndex)));
new QualifierDefinitionProvider(springIndex),
new DataQueryParameterDefinitionProvider(server.getTextDocumentService(), qurySemanticTokens)));
}
@Bean

View File

@@ -20,7 +20,9 @@ import org.springframework.ide.vscode.boot.java.cron.CronSemanticTokens;
import org.springframework.ide.vscode.boot.java.cron.JdtCronReconciler;
import org.springframework.ide.vscode.boot.java.cron.JdtCronSemanticTokensProvider;
import org.springframework.ide.vscode.boot.java.data.jpa.queries.HqlSemanticTokens;
import org.springframework.ide.vscode.boot.java.data.jpa.queries.JdtDataQueriesInlayHintsProvider;
import org.springframework.ide.vscode.boot.java.data.jpa.queries.JdtDataQuerySemanticTokensProvider;
import org.springframework.ide.vscode.boot.java.data.jpa.queries.JdtQueryDocHighlightsProvider;
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.data.jpa.queries.QueryJdtAstReconciler;
@@ -130,6 +132,14 @@ public class JdtConfig {
return new JdtDataQuerySemanticTokensProvider(jpqlProvider, hqlProvider, supportState, spelSemanticTokens);
}
@Bean JdtDataQueriesInlayHintsProvider jdtDataQueriesInlayHintsProvider(JdtDataQuerySemanticTokensProvider semanticTokensProvider) {
return new JdtDataQueriesInlayHintsProvider(semanticTokensProvider);
}
@Bean JdtQueryDocHighlightsProvider jdtDocHighlightsProvider(JdtDataQuerySemanticTokensProvider semanticTokensProvider) {
return new JdtQueryDocHighlightsProvider(semanticTokensProvider);
}
@Bean JdtCronSemanticTokensProvider jdtCronSemanticTokensProvider(CronSemanticTokens cronProvider) {
return new JdtCronSemanticTokensProvider(cronProvider);
}

View File

@@ -71,6 +71,7 @@ import org.springframework.ide.vscode.commons.languageserver.util.CodeLensHandle
import org.springframework.ide.vscode.commons.languageserver.util.DocumentHighlightHandler;
import org.springframework.ide.vscode.commons.languageserver.util.DocumentSymbolHandler;
import org.springframework.ide.vscode.commons.languageserver.util.HoverHandler;
import org.springframework.ide.vscode.commons.languageserver.util.InlayHintHandler;
import org.springframework.ide.vscode.commons.languageserver.util.ReferencesHandler;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
@@ -116,6 +117,7 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
private BootJavaCodeActionProvider codeActionProvider;
private DocumentSymbolHandler docSymbolProvider;
private JdtSemanticTokensHandler semanticTokensHandler;
private JdtInlayHintsHandler inlayHintsHandler;
public BootJavaLanguageServerComponents(ApplicationContext appContext) {
this.server = appContext.getBean(SimpleLanguageServer.class);
@@ -176,7 +178,7 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
codeLensHandler = createCodeLensEngine(springSymbolIndex, projectFinder, server);
highlightsEngine = createDocumentHighlightEngine(springSymbolIndex);
highlightsEngine = createDocumentHighlightEngine(appContext);
documents.onDocumentHighlight(highlightsEngine);
Map<String, JdtSemanticTokensProvider> jdtSemanticTokensProviders = appContext.getBeansOfType(JdtSemanticTokensProvider.class);
@@ -184,6 +186,11 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
semanticTokensHandler = new JdtSemanticTokensHandler(cuCache, projectFinder, jdtSemanticTokensProviders.values());
}
Map<String, JdtInlayHintsProvider> jdtInlayHintsProviders = appContext.getBeansOfType(JdtInlayHintsProvider.class);
if (!jdtSemanticTokensProviders.isEmpty()) {
inlayHintsHandler = new JdtInlayHintsHandler(cuCache, projectFinder, jdtInlayHintsProviders.values());
}
config.addListener(ignore -> {
log.info("update live process tracker settings - start");
@@ -313,9 +320,14 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
return new BootJavaCodeLensEngine(this, codeLensProvider);
}
protected BootJavaDocumentHighlightEngine createDocumentHighlightEngine(SpringSymbolIndex indexer) {
protected BootJavaDocumentHighlightEngine createDocumentHighlightEngine(ApplicationContext appContext) {
Collection<HighlightProvider> highlightProvider = new ArrayList<>();
highlightProvider.add(new WebfluxRouteHighlightProdivder(indexer));
highlightProvider.add(new WebfluxRouteHighlightProdivder(appContext.getBean(SpringSymbolIndex.class)));
Map<String, JdtAstDocHighlightsProvider> astHighlightProviders = appContext.getBeansOfType(JdtAstDocHighlightsProvider.class);
if (!astHighlightProviders.isEmpty()) {
highlightProvider.add(new JdtDocHighlightsProvider(projectFinder, cuCache, astHighlightProviders.values()));
}
return new BootJavaDocumentHighlightEngine(this, highlightProvider);
}
@@ -359,5 +371,10 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
return Optional.ofNullable(semanticTokensHandler);
}
@Override
public Optional<InlayHintHandler> getInlayHintHandler() {
return Optional.ofNullable(inlayHintsHandler);
}
}

View File

@@ -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
@@ -15,11 +15,12 @@ import java.util.List;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.lsp4j.LocationLink;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
import org.springframework.ide.vscode.commons.java.IJavaProject;
public interface IJavaDefinitionProvider {
List<LocationLink> getDefinitions(CancelChecker cancelToken, IJavaProject project, CompilationUnit cu, ASTNode n);
List<LocationLink> getDefinitions(CancelChecker cancelToken, IJavaProject project, TextDocumentIdentifier docId, CompilationUnit cu, ASTNode n, int offset);
}

View File

@@ -21,6 +21,8 @@ import org.eclipse.lsp4j.DefinitionParams;
import org.eclipse.lsp4j.LocationLink;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;
@@ -33,6 +35,8 @@ import com.google.common.collect.ImmutableList.Builder;
public class JavaDefinitionHandler implements DefinitionHandler, LanguageSpecific {
private static final Logger log = LoggerFactory.getLogger(JavaDefinitionHandler.class);
private CompilationUnitCache cuCache;
private JavaProjectFinder projectFinder;
private Collection<IJavaDefinitionProvider> providers;
@@ -64,7 +68,11 @@ public class JavaDefinitionHandler implements DefinitionHandler, LanguageSpecifi
if (cancelToken.isCanceled()) {
break;
}
builder.addAll(provider.getDefinitions(cancelToken, project, cu, node));
try {
builder.addAll(provider.getDefinitions(cancelToken, project, doc, cu, node, start));
} catch (Exception e) {
log.error("", e);
}
}
}
return builder.build();

View File

@@ -0,0 +1,25 @@
/*******************************************************************************
* 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.List;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.lsp4j.DocumentHighlight;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
public interface JdtAstDocHighlightsProvider {
List<DocumentHighlight> getDocHighlights(IJavaProject project, TextDocument doc, CompilationUnit cu, ASTNode node, int offset);
}

View File

@@ -0,0 +1,70 @@
/*******************************************************************************
* 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 org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.NodeFinder;
import org.eclipse.lsp4j.DocumentHighlight;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.java.handlers.HighlightProvider;
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.util.text.TextDocument;
public class JdtDocHighlightsProvider implements HighlightProvider {
private static final Logger log = LoggerFactory.getLogger(JdtDocHighlightsProvider.class);
private final JavaProjectFinder projectFinder;
private final CompilationUnitCache cuCache;
private final Collection<JdtAstDocHighlightsProvider> astHighlightProviders;
public JdtDocHighlightsProvider(JavaProjectFinder projectFinder, CompilationUnitCache cuCache, Collection<JdtAstDocHighlightsProvider> astHighlightProviders) {
this.projectFinder = projectFinder;
this.cuCache = cuCache;
this.astHighlightProviders = astHighlightProviders;
}
@Override
public void provideHighlights(CancelChecker cancelToken, TextDocument doc, Position p,
List<DocumentHighlight> resultAccumulator) {
if (!astHighlightProviders.isEmpty()) {
IJavaProject project = projectFinder.find(doc.getId()).orElse(null);
if (project != null) {
URI docUri = URI.create(doc.getUri());
cuCache.withCompilationUnit(project, docUri, cu -> {
if (cu != null) {
int start = cu.getPosition(p.getLine() + 1, p.getCharacter());
ASTNode node = NodeFinder.perform(cu, start, 0);
for (JdtAstDocHighlightsProvider provider : astHighlightProviders) {
try {
resultAccumulator.addAll(provider.getDocHighlights(project, doc, cu, node, start));
} catch (Throwable t) {
log.error("", t);
}
}
}
return null;
});
}
}
}
}

View File

@@ -0,0 +1,75 @@
/*******************************************************************************
* 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.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.lsp4j.InlayHint;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
import org.springframework.ide.vscode.boot.java.reconcilers.CompositeASTVisitor;
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.util.InlayHintHandler;
import org.springframework.ide.vscode.commons.util.Collector;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
public class JdtInlayHintsHandler implements InlayHintHandler {
private final CompilationUnitCache cuCache;
private final JavaProjectFinder projectFinder;
private final Collection<JdtInlayHintsProvider> inlayHintsProviders;
public JdtInlayHintsHandler(CompilationUnitCache cuCache, JavaProjectFinder projectFinder, Collection<JdtInlayHintsProvider> inlayHintsProviders) {
this.cuCache = cuCache;
this.projectFinder = projectFinder;
this.inlayHintsProviders = inlayHintsProviders;
}
@Override
public List<InlayHint> handle(TextDocument doc, Range range, CancelChecker cancelChecker) {
Optional<IJavaProject> optProject = projectFinder.find(doc.getId());
if (optProject.isPresent()) {
IJavaProject jp = optProject.get();
List<JdtInlayHintsProvider> applicableInlayHintsProviders = inlayHintsProviders.stream().filter(tp -> tp.isApplicable(jp)).collect(Collectors.toList());
if (!applicableInlayHintsProviders.isEmpty()) {
return cuCache.withCompilationUnit(jp, URI.create(doc.getUri()), cu -> computeInlayHints(applicableInlayHintsProviders, jp, cu, range, doc));
}
}
return Collections.emptyList();
}
private List<InlayHint> computeInlayHints(List<JdtInlayHintsProvider> applicableInlayHintsProviders, IJavaProject jp, CompilationUnit cu, Range r, TextDocument doc) {
if (cu == null) {
return null;
}
Collector<InlayHint> collector = new Collector<>();
CompositeASTVisitor visitor = new CompositeASTVisitor();
applicableInlayHintsProviders.forEach(p -> visitor.add(p.getInlayHintsComputer(jp, doc, cu, collector)));
if (r != null) {
if (r.getStart() != null) {
visitor.setStartOffset(cu.getPosition(r.getStart().getLine(), r.getStart().getCharacter()));
}
if (r.getEnd() != null) {
visitor.setEndOffset(cu.getPosition(r.getEnd().getLine(), r.getEnd().getCharacter()));
}
}
cu.accept(visitor);
return collector.get();
}
}

View File

@@ -0,0 +1,25 @@
/*******************************************************************************
* 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 org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.lsp4j.InlayHint;
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;
public interface JdtInlayHintsProvider {
boolean isApplicable(IJavaProject project);
ASTVisitor getInlayHintsComputer(IJavaProject project, TextDocument doc, CompilationUnit cu, Collector<InlayHint> collector);
}

View File

@@ -21,6 +21,7 @@ import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IAnnotationBinding;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.lsp4j.LocationLink;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
import org.springframework.ide.vscode.boot.java.Annotations;
@@ -41,7 +42,7 @@ public class DependsOnDefinitionProvider implements IJavaDefinitionProvider {
}
@Override
public List<LocationLink> getDefinitions(CancelChecker cancelToken, IJavaProject project, CompilationUnit cu, ASTNode n) {
public List<LocationLink> getDefinitions(CancelChecker cancelToken, IJavaProject project, TextDocumentIdentifier docId, CompilationUnit cu, ASTNode n, int offset) {
if (n instanceof StringLiteral) {
StringLiteral valueNode = (StringLiteral) n;

View File

@@ -21,6 +21,7 @@ import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IAnnotationBinding;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.lsp4j.LocationLink;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
import org.springframework.ide.vscode.boot.java.Annotations;
@@ -41,7 +42,7 @@ public class QualifierDefinitionProvider implements IJavaDefinitionProvider {
}
@Override
public List<LocationLink> getDefinitions(CancelChecker cancelToken, IJavaProject project, CompilationUnit cu, ASTNode n) {
public List<LocationLink> getDefinitions(CancelChecker cancelToken, IJavaProject project, TextDocumentIdentifier docId, CompilationUnit cu, ASTNode n, int offset) {
if (n instanceof StringLiteral) {
StringLiteral valueNode = (StringLiteral) n;

View File

@@ -21,6 +21,7 @@ import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IAnnotationBinding;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.lsp4j.LocationLink;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
import org.springframework.ide.vscode.boot.java.Annotations;
@@ -41,7 +42,7 @@ public class ResourceDefinitionProvider implements IJavaDefinitionProvider {
}
@Override
public List<LocationLink> getDefinitions(CancelChecker cancelToken, IJavaProject project, CompilationUnit cu, ASTNode n) {
public List<LocationLink> getDefinitions(CancelChecker cancelToken, IJavaProject project, TextDocumentIdentifier docId, CompilationUnit cu, ASTNode n, int offset) {
if (n instanceof StringLiteral) {
StringLiteral valueNode = (StringLiteral) n;

View File

@@ -14,11 +14,8 @@ import java.net.URI;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MemberValuePair;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.springframework.ide.vscode.boot.java.Annotations;
import org.springframework.ide.vscode.boot.java.data.jpa.queries.QueryJdtAstReconciler;
import org.springframework.ide.vscode.boot.java.data.jpa.queries.JdtQueryVisitorUtils.EmbeddedExpression;
import org.springframework.ide.vscode.boot.java.reconcilers.JdtAstReconciler;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.java.SpringProjectUtil;
@@ -49,22 +46,9 @@ public class JdtCronReconciler implements JdtAstReconciler {
return new ASTVisitor() {
@Override
public boolean visit(NormalAnnotation node) {
if (node.getTypeName() != null) {
String fqn = node.getTypeName().getFullyQualifiedName();
if (JdtCronSemanticTokensProvider.SCHEDULED_SIMPLE_NAME.equals(fqn) || Annotations.SCHEDULED.equals(fqn)) {
ITypeBinding typeBinding = node.resolveTypeBinding();
if (typeBinding != null && Annotations.SCHEDULED.equals(typeBinding.getQualifiedName())) {
for (Object value : node.values()) {
if (value instanceof MemberValuePair) {
MemberValuePair pair = (MemberValuePair) value;
String name = pair.getName().getFullyQualifiedName();
if (name != null && "cron".equals(name) && JdtCronSemanticTokensProvider.isCronExpression(pair.getValue())) {
QueryJdtAstReconciler.reconcileExpression(cronReconciler, pair.getValue(), problemCollector);
}
}
}
}
}
EmbeddedExpression e = JdtCronVisitorUtils.extractCron(node);
if (e != null) {
cronReconciler.reconcile(e.text(), e.offset(), problemCollector);
}
return super.visit(node);
}

View File

@@ -14,15 +14,9 @@ import java.util.List;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MemberValuePair;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.jdt.core.dom.TextBlock;
import org.springframework.ide.vscode.boot.java.Annotations;
import org.springframework.ide.vscode.boot.java.JdtSemanticTokensProvider;
import org.springframework.ide.vscode.boot.java.data.jpa.queries.JdtDataQuerySemanticTokensProvider;
import org.springframework.ide.vscode.boot.java.data.jpa.queries.JdtQueryVisitorUtils.EmbeddedExpression;
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;
@@ -62,22 +56,9 @@ public class JdtCronSemanticTokensProvider implements JdtSemanticTokensProvider
@Override
public boolean visit(NormalAnnotation node) {
if (node.getTypeName() != null) {
String fqn = node.getTypeName().getFullyQualifiedName();
if (SCHEDULED_SIMPLE_NAME.equals(fqn) || Annotations.SCHEDULED.equals(fqn)) {
ITypeBinding typeBinding = node.resolveTypeBinding();
if (typeBinding != null && Annotations.SCHEDULED.equals(typeBinding.getQualifiedName())) {
for (Object value : node.values()) {
if (value instanceof MemberValuePair) {
MemberValuePair pair = (MemberValuePair) value;
String name = pair.getName().getFullyQualifiedName();
if (name != null && "cron".equals(name) && isCronExpression(pair.getValue())) {
JdtDataQuerySemanticTokensProvider.computeTokensForExpression(tokensProvider, pair.getValue()).forEach(collector::accept);
}
}
}
}
}
EmbeddedExpression e = JdtCronVisitorUtils.extractCron(node);
if (e != null) {
tokensProvider.computeTokens(e.text(), e.offset()).forEach(collector::accept);
}
return super.visit(node);
}
@@ -85,19 +66,4 @@ public class JdtCronSemanticTokensProvider implements JdtSemanticTokensProvider
};
}
public static boolean isCronExpression(Expression e) {
String value = null;
if (e instanceof StringLiteral sl) {
value = sl.getLiteralValue();
} else if (e instanceof TextBlock tb) {
value = tb.getLiteralValue();
}
value = value.trim();
if (value.startsWith("#{") || value.startsWith("${")) {
// Either SPEL or Property Holder
return false;
}
return value != null;
}
}

View File

@@ -0,0 +1,65 @@
/*******************************************************************************
* 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.cron;
import static org.springframework.ide.vscode.boot.java.data.jpa.queries.JdtQueryVisitorUtils.extractEmbeddedExpression;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MemberValuePair;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.jdt.core.dom.TextBlock;
import org.springframework.ide.vscode.boot.java.Annotations;
import org.springframework.ide.vscode.boot.java.data.jpa.queries.JdtQueryVisitorUtils.EmbeddedExpression;
public class JdtCronVisitorUtils {
static final String SCHEDULED_SIMPLE_NAME = "Scheduled";
public static EmbeddedExpression extractCron(NormalAnnotation node) {
if (node.getTypeName() != null) {
String fqn = node.getTypeName().getFullyQualifiedName();
if (SCHEDULED_SIMPLE_NAME.equals(fqn) || Annotations.SCHEDULED.equals(fqn)) {
ITypeBinding typeBinding = node.resolveTypeBinding();
if (typeBinding != null && Annotations.SCHEDULED.equals(typeBinding.getQualifiedName())) {
for (Object value : node.values()) {
if (value instanceof MemberValuePair) {
MemberValuePair pair = (MemberValuePair) value;
String name = pair.getName().getFullyQualifiedName();
if (name != null && "cron".equals(name) && isCronExpression(pair.getValue())) {
return extractEmbeddedExpression(pair.getValue());
}
}
}
}
}
}
return null;
}
private static boolean isCronExpression(Expression e) {
String value = null;
if (e instanceof StringLiteral sl) {
value = sl.getLiteralValue();
} else if (e instanceof TextBlock tb) {
value = tb.getLiteralValue();
}
value = value.trim();
if (value.startsWith("#{") || value.startsWith("${")) {
// Either SPEL or Property Holder
return false;
}
return value != null;
}
}

View File

@@ -0,0 +1,83 @@
/*******************************************************************************
* 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.Collections;
import java.util.List;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.jdt.core.dom.TextBlock;
import org.eclipse.lsp4j.LocationLink;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.java.IJavaDefinitionProvider;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.semantic.tokens.SemanticTokenData;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.Collector;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
public class DataQueryParameterDefinitionProvider implements IJavaDefinitionProvider {
private static final Logger log = LoggerFactory.getLogger(DataQueryParameterDefinitionProvider.class);
private final JdtDataQuerySemanticTokensProvider semanticTokensProvider;
private final SimpleTextDocumentService documents;
public DataQueryParameterDefinitionProvider(SimpleTextDocumentService documents, JdtDataQuerySemanticTokensProvider semanticTokensProvider) {
this.documents = documents;
this.semanticTokensProvider = semanticTokensProvider;
}
@Override
public List<LocationLink> getDefinitions(CancelChecker cancelToken, IJavaProject project,
TextDocumentIdentifier docId, CompilationUnit cu, ASTNode n, int offset) {
if (n instanceof StringLiteral || n instanceof TextBlock) {
ASTNode a = JdtQueryDocHighlightsProvider.findQueryAnnotation(n);
TextDocument doc = documents.getLatestSnapshot(docId.getUri());
if (a.getParent() instanceof MethodDeclaration m && !m.parameters().isEmpty()) {
Collector<SemanticTokenData> collector = new Collector<>();
a.accept(semanticTokensProvider.getTokensComputer(project, doc, cu, collector));
for (SemanticTokenData t : collector.get()) {
if ("parameter".equals(t.type()) && t.start() <= offset && offset <= t.end()) {
try {
String parameterDescriptor = doc.get(t.start(), t.end() - t.start());
SimpleName paramName = JdtQueryDocHighlightsProvider.findParameter(m, parameterDescriptor);
if (paramName != null) {
LocationLink link = new LocationLink();
link.setTargetUri(docId.getUri());
link.setOriginSelectionRange(doc.toRange(t.start(), t.end() - t.start()));
link.setTargetSelectionRange(doc.toRange(paramName.getStartPosition(), paramName.getLength()));
link.setTargetRange(link.getTargetSelectionRange());
return List.of(link);
}
} catch (BadLocationException e) {
log.error("", e);
}
}
}
}
}
return Collections.emptyList();
}
}

View File

@@ -146,6 +146,9 @@ public class HqlSemanticTokens implements SemanticTokensDataProvider {
if (ctx.identifier() != null && ctx.identifier().getStart() != null) {
semantics.put(ctx.identifier().getStart(), "parameter");
}
if (ctx.INTEGER_LITERAL() != null) {
semantics.put(ctx.INTEGER_LITERAL().getSymbol(), "parameter");
}
}
});

View File

@@ -0,0 +1,104 @@
/*******************************************************************************
* 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.List;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.lsp4j.InlayHint;
import org.eclipse.lsp4j.InlayHintKind;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.springframework.ide.vscode.boot.java.JdtInlayHintsProvider;
import org.springframework.ide.vscode.boot.java.data.jpa.queries.JdtQueryVisitorUtils.EmbeddedQueryExpression;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.semantic.tokens.SemanticTokenData;
import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.Collector;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
public class JdtDataQueriesInlayHintsProvider implements JdtInlayHintsProvider {
private final JdtDataQuerySemanticTokensProvider semanticTokensProvider;
public JdtDataQueriesInlayHintsProvider(JdtDataQuerySemanticTokensProvider semanticTokensProvider) {
this.semanticTokensProvider = semanticTokensProvider;
}
@Override
public boolean isApplicable(IJavaProject project) {
return semanticTokensProvider.isApplicable(project);
}
@Override
public ASTVisitor getInlayHintsComputer(IJavaProject project, TextDocument doc, CompilationUnit cu,
Collector<InlayHint> collector) {
return new ASTVisitor() {
@Override
public boolean visit(NormalAnnotation node) {
if (node.getParent() instanceof MethodDeclaration m && !m.parameters().isEmpty()) {
EmbeddedQueryExpression q = JdtQueryVisitorUtils.extractQueryExpression(node);
if (q != null) {
processQuery(project, doc, collector, m, q);
}
}
return super.visit(node);
}
@Override
public boolean visit(SingleMemberAnnotation node) {
if (node.getParent() instanceof MethodDeclaration m && !m.parameters().isEmpty()) {
EmbeddedQueryExpression q = JdtQueryVisitorUtils.extractQueryExpression(node);
if (q != null) {
processQuery(project, doc, collector, m, q);
}
}
return super.visit(node);
}
};
}
private void processQuery(IJavaProject project, TextDocument doc, Collector<InlayHint> collector, MethodDeclaration m, EmbeddedQueryExpression q) {
List<SemanticTokenData> semanticTokens = semanticTokensProvider.computeSemanticTokens(project, q.query().text(), q.query().offset(), q.isNative());
for (SemanticTokenData t : semanticTokens) {
if ("parameter".equals(t.type())) {
try {
int number = Integer.parseInt(doc.get(t.start(), t.end() - t.start()));
if (number > 0 && number <= m.parameters().size()) {
Object param = m.parameters().get(number - 1);
if (param instanceof SingleVariableDeclaration svd) {
String paramName = svd.getName().getIdentifier();
InlayHint hint = new InlayHint();
hint.setKind(InlayHintKind.Parameter);
hint.setLabel(Either.forLeft(paramName));
hint.setPaddingLeft(true);
hint.setPosition(doc.toPosition(t.end()));
collector.accept(hint);
}
}
} catch (NumberFormatException e) {
// ignore
} catch (BadLocationException e) {
// ignore
}
}
}
}
}

View File

@@ -19,20 +19,12 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
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.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
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.eclipse.jdt.core.dom.TextBlock;
import org.springframework.ide.vscode.boot.java.Annotations;
import org.springframework.ide.vscode.boot.java.JdtSemanticTokensProvider;
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies;
import org.springframework.ide.vscode.boot.java.data.jpa.queries.JdtQueryVisitorUtils.EmbeddedQueryExpression;
import org.springframework.ide.vscode.boot.java.spel.SpelSemanticTokens;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.java.SpringProjectUtil;
@@ -43,9 +35,6 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument;
public class JdtDataQuerySemanticTokensProvider implements JdtSemanticTokensProvider {
private static final String QUERY = "Query";
private static final String NAMED_QUERY = "NamedQuery";
private final JpqlSemanticTokens jpqlProvider;
private final HqlSemanticTokens hqlProvider;
private final JpqlSupportState supportState;
@@ -74,126 +63,43 @@ public class JdtDataQuerySemanticTokensProvider implements JdtSemanticTokensProv
@Override
public ASTVisitor getTokensComputer(IJavaProject jp, TextDocument doc, CompilationUnit cu, Collector<SemanticTokenData> tokensData) {
SemanticTokensDataProvider provider = SpringProjectUtil.hasDependencyStartingWith(jp, "hibernate-core", null) ? hqlProvider : jpqlProvider;
return new ASTVisitor() {
@Override
public boolean visit(NormalAnnotation a) {
Expression queryExpression = null;
boolean isNative = false;
if (isQueryAnnotation(a)) {
for (Object value : a.values()) {
if (value instanceof MemberValuePair) {
MemberValuePair pair = (MemberValuePair) value;
String name = pair.getName().getFullyQualifiedName();
if (name != null) {
switch (name) {
case "value":
queryExpression = pair.getValue();
break;
case "nativeQuery":
Expression expression = pair.getValue();
if (expression != null) {
Object o = expression.resolveConstantExpressionValue();
if (o instanceof Boolean b) {
isNative = b.booleanValue();
}
}
break;
}
}
}
}
} else if (isNamedQueryAnnotation(a)) {
for (Object value : a.values()) {
if (value instanceof MemberValuePair) {
MemberValuePair pair = (MemberValuePair) value;
String name = pair.getName().getFullyQualifiedName();
if (name != null) {
switch (name) {
case "query":
queryExpression = pair.getValue();
break;
}
}
}
}
EmbeddedQueryExpression q = JdtQueryVisitorUtils.extractQueryExpression(a);
if (q != null) {
computeSemanticTokens(jp, q.query().text(), q.query().offset(), q.isNative()).forEach(tokensData::accept);
}
if (queryExpression != null) {
if (isNative) {
computeTokensForExpression(getSqlSemanticTokensProvider(jp), queryExpression).forEach(tokensData::accept);
} else {
computeTokensForExpression(provider, queryExpression).forEach(tokensData::accept);
}
}
return false;
return super.visit(a);
}
@Override
public boolean visit(SingleMemberAnnotation a) {
if (isQueryAnnotation(a)) {
computeTokensForExpression(provider, a.getValue()).forEach(tokensData::accept);
EmbeddedQueryExpression q = JdtQueryVisitorUtils.extractQueryExpression(a);
if (q != null) {
computeSemanticTokens(jp, q.query().text(), q.query().offset(), q.isNative()).forEach(tokensData::accept);
}
return false;
return super.visit(a);
}
@Override
public boolean visit(MethodInvocation node) {
if ("createQuery".equals(node.getName().getIdentifier()) && node.arguments().size() <= 2 && node.arguments().get(0) instanceof Expression 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())) {
computeTokensForExpression(provider, queryExpr).forEach(tokensData::accept);
}
}
EmbeddedQueryExpression q = JdtQueryVisitorUtils.extractQueryExpression(node);
if (q != null) {
computeSemanticTokens(jp, q.query().text(), q.query().offset(), q.isNative()).forEach(tokensData::accept);
}
return super.visit(node);
}
};
}
public static List<SemanticTokenData> computeTokensForExpression(SemanticTokensDataProvider provider, Expression valueExp) {
String query = null;
int offset = 0;
if (valueExp instanceof StringLiteral sl) {
query = sl.getEscapedValue();
query = query.substring(1, query.length() - 1);
offset = sl.getStartPosition() + 1; // +1 to skip over opening "
} else if (valueExp instanceof TextBlock tb) {
query = tb.getEscapedValue();
query = query.substring(3, query.length() - 3);
offset = tb.getStartPosition() + 3; // +3 to skip over opening """
}
if (query != null) {
public List<SemanticTokenData> computeSemanticTokens(IJavaProject jp, String query, int offset, boolean isNative) {
SemanticTokensDataProvider provider = isNative ? getSqlSemanticTokensProvider(jp) : (SpringProjectUtil.hasDependencyStartingWith(jp, "hibernate-core", null) ? hqlProvider : jpqlProvider);
if (provider != null) {
return provider.computeTokens(query, offset);
}
return Collections.emptyList();
}
static boolean isQueryAnnotation(Annotation a) {
if (Annotations.DATA_QUERY.equals(a.getTypeName().getFullyQualifiedName()) || QUERY.equals(a.getTypeName().getFullyQualifiedName())) {
ITypeBinding type = a.resolveTypeBinding();
if (type != null) {
return AnnotationHierarchies.hasTransitiveSuperAnnotationType(type, Annotations.DATA_QUERY);
}
}
return false;
}
static boolean isNamedQueryAnnotation(Annotation a) {
if (NAMED_QUERY.equals(a.getTypeName().getFullyQualifiedName()) || Annotations.JPA_JAKARTA_NAMED_QUERY.equals(a.getTypeName().getFullyQualifiedName())
|| Annotations.JPA_JAVAX_NAMED_QUERY.equals(a.getTypeName().getFullyQualifiedName())) {
ITypeBinding type = a.resolveTypeBinding();
if (type != null) {
return AnnotationHierarchies.hasTransitiveSuperAnnotationType(type, Annotations.JPA_JAKARTA_NAMED_QUERY)
|| AnnotationHierarchies.hasTransitiveSuperAnnotationType(type, Annotations.JPA_JAVAX_NAMED_QUERY);
}
}
return false;
}
@Override
public boolean isApplicable(IJavaProject project) {

View File

@@ -0,0 +1,111 @@
/*******************************************************************************
* 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.Collections;
import java.util.List;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.MemberValuePair;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.jdt.core.dom.TextBlock;
import org.eclipse.jdt.core.dom.VariableDeclaration;
import org.eclipse.lsp4j.DocumentHighlight;
import org.eclipse.lsp4j.DocumentHighlightKind;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.java.JdtAstDocHighlightsProvider;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.semantic.tokens.SemanticTokenData;
import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.Collector;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
public class JdtQueryDocHighlightsProvider implements JdtAstDocHighlightsProvider {
private static final Logger log = LoggerFactory.getLogger(JdtQueryDocHighlightsProvider.class);
private JdtDataQuerySemanticTokensProvider semanticTokensProvider;
public JdtQueryDocHighlightsProvider(JdtDataQuerySemanticTokensProvider semanticTokensProvider) {
this.semanticTokensProvider = semanticTokensProvider;
}
@Override
public List<DocumentHighlight> getDocHighlights(IJavaProject project, TextDocument doc, CompilationUnit cu,
ASTNode node, int offset) {
if (node instanceof StringLiteral || node instanceof TextBlock) {
Annotation a = findQueryAnnotation(node);
if (a != null && a.getParent() instanceof MethodDeclaration m && !m.parameters().isEmpty()) {
Collector<SemanticTokenData> collector = new Collector<>();
a.accept(semanticTokensProvider.getTokensComputer(project, doc, cu, collector));
for (SemanticTokenData t : collector.get()) {
if ("parameter".equals(t.type()) && t.start() <= offset && offset <= t.end()) {
try {
String parameterDescriptor = doc.get(t.start(), t.end() - t.start());
SimpleName paramName = findParameter(m, parameterDescriptor);
if (paramName != null) {
DocumentHighlight highlight = new DocumentHighlight();
highlight.setKind(DocumentHighlightKind.Write);
highlight.setRange(doc.toRange(paramName.getStartPosition(), paramName.getLength()));
return List.of(highlight);
}
} catch (BadLocationException e) {
log.error("", e);
}
}
}
}
}
return Collections.emptyList();
}
static Annotation findQueryAnnotation(ASTNode node) {
if (node.getParent() instanceof MemberValuePair pair
&& node.getParent().getParent() instanceof NormalAnnotation na && "value".equals(pair.getName().getIdentifier())
&& JdtQueryVisitorUtils.isQueryAnnotation(na)) {
return na;
} else if (node.getParent() instanceof SingleMemberAnnotation sm
&& JdtQueryVisitorUtils.isQueryAnnotation(sm)) {
return sm;
}
return null;
}
static SimpleName findParameter(MethodDeclaration m, String p) {
try {
int paramNumber = Integer.parseInt(p);
if (paramNumber > 0 && paramNumber <= m.parameters().size()) {
Object o = m.parameters().get(paramNumber - 1);
if (o instanceof VariableDeclaration vd) {
return vd.getName();
}
}
} catch (NumberFormatException e) {
for (Object o : m.parameters()) {
if (o instanceof VariableDeclaration vd) {
SimpleName simpleName = vd.getName();
if (simpleName != null && p.equals(simpleName.getIdentifier())) {
return simpleName;
}
}
}
}
return null;
}
}

View File

@@ -0,0 +1,145 @@
/*******************************************************************************
* 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.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
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.eclipse.jdt.core.dom.TextBlock;
import org.springframework.ide.vscode.boot.java.Annotations;
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies;
public class JdtQueryVisitorUtils {
private static final String QUERY = "Query";
private static final String NAMED_QUERY = "NamedQuery";
public record EmbeddedExpression(Expression expression, String text, int offset) {};
public record EmbeddedQueryExpression(EmbeddedExpression query, boolean isNative) {};
public static EmbeddedQueryExpression extractQueryExpression(SingleMemberAnnotation a) {
if (isQueryAnnotation(a)) {
EmbeddedExpression expression = extractEmbeddedExpression(a.getValue());
return expression == null ? null : new EmbeddedQueryExpression(expression, false);
}
return null;
}
public static EmbeddedQueryExpression extractQueryExpression(NormalAnnotation a) {
Expression queryExpression = null;
boolean isNative = false;
if (isQueryAnnotation(a)) {
for (Object value : a.values()) {
if (value instanceof MemberValuePair) {
MemberValuePair pair = (MemberValuePair) value;
String name = pair.getName().getFullyQualifiedName();
if (name != null) {
switch (name) {
case "value":
queryExpression = pair.getValue();
break;
case "nativeQuery":
Expression expression = pair.getValue();
if (expression != null) {
Object o = expression.resolveConstantExpressionValue();
if (o instanceof Boolean b) {
isNative = b.booleanValue();
}
}
break;
}
}
}
}
} else if (isNamedQueryAnnotation(a)) {
for (Object value : a.values()) {
if (value instanceof MemberValuePair) {
MemberValuePair pair = (MemberValuePair) value;
String name = pair.getName().getFullyQualifiedName();
if (name != null) {
switch (name) {
case "query":
queryExpression = pair.getValue();
break;
}
}
}
}
}
if (queryExpression != null) {
EmbeddedExpression e = extractEmbeddedExpression(queryExpression);
if (e != null) {
return new EmbeddedQueryExpression(e, isNative);
}
}
return null;
}
public static EmbeddedQueryExpression extractQueryExpression(MethodInvocation m) {
if ("createQuery".equals(m.getName().getIdentifier()) && m.arguments().size() <= 2 && m.arguments().get(0) instanceof Expression queryExpr) {
IMethodBinding methodBinding = m.resolveMethodBinding();
if ("jakarta.persistence.EntityManager".equals(methodBinding.getDeclaringClass().getQualifiedName())) {
if (methodBinding.getParameterTypes().length <= 2 && "java.lang.String".equals(methodBinding.getParameterTypes()[0].getQualifiedName())) {
EmbeddedExpression expression = extractEmbeddedExpression(queryExpr);
return expression == null ? null : new EmbeddedQueryExpression(expression, false);
}
}
}
return null;
}
public static EmbeddedExpression extractEmbeddedExpression(Expression valueExp) {
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);
offset = tb.getStartPosition() + 3; // +3 to skip over opening """
}
return text == null ? null : new EmbeddedExpression(valueExp, text, offset);
}
static boolean isQueryAnnotation(Annotation a) {
if (Annotations.DATA_QUERY.equals(a.getTypeName().getFullyQualifiedName()) || QUERY.equals(a.getTypeName().getFullyQualifiedName())) {
ITypeBinding type = a.resolveTypeBinding();
if (type != null) {
return AnnotationHierarchies.hasTransitiveSuperAnnotationType(type, Annotations.DATA_QUERY);
}
}
return false;
}
static boolean isNamedQueryAnnotation(Annotation a) {
if (NAMED_QUERY.equals(a.getTypeName().getFullyQualifiedName()) || Annotations.JPA_JAKARTA_NAMED_QUERY.equals(a.getTypeName().getFullyQualifiedName())
|| Annotations.JPA_JAVAX_NAMED_QUERY.equals(a.getTypeName().getFullyQualifiedName())) {
ITypeBinding type = a.resolveTypeBinding();
if (type != null) {
return AnnotationHierarchies.hasTransitiveSuperAnnotationType(type, Annotations.JPA_JAKARTA_NAMED_QUERY)
|| AnnotationHierarchies.hasTransitiveSuperAnnotationType(type, Annotations.JPA_JAVAX_NAMED_QUERY);
}
}
return false;
}
}

View File

@@ -157,6 +157,9 @@ public class JpqlSemanticTokens implements SemanticTokensDataProvider {
if (ctx.identification_variable() != null && ctx.identification_variable().getStart() != null) {
semantics.put(ctx.identification_variable().getStart(), "parameter");
}
if (ctx.INTLITERAL() != null) {
semantics.put(ctx.INTLITERAL().getSymbol(), "parameter");
}
}
@Override

View File

@@ -18,13 +18,12 @@ import java.util.Optional;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Expression;
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.eclipse.jdt.core.dom.TextBlock;
import org.springframework.ide.vscode.boot.java.data.jpa.queries.JdtQueryVisitorUtils.EmbeddedQueryExpression;
import org.springframework.ide.vscode.boot.java.handlers.Reconciler;
import org.springframework.ide.vscode.boot.java.reconcilers.JdtAstReconciler;
import org.springframework.ide.vscode.boot.java.reconcilers.RequiredCompleteAstException;
@@ -61,77 +60,28 @@ public class QueryJdtAstReconciler implements JdtAstReconciler {
@Override
public boolean visit(NormalAnnotation node) {
Expression queryExpression = null;
boolean isNative = false;
if (JdtDataQuerySemanticTokensProvider.isQueryAnnotation(node)) {
for (Object value : node.values()) {
if (value instanceof MemberValuePair) {
MemberValuePair pair = (MemberValuePair) value;
String name = pair.getName().getFullyQualifiedName();
if (name != null) {
switch (name) {
case "value":
queryExpression = pair.getValue();
break;
case "nativeQuery":
Expression expression = pair.getValue();
if (expression != null) {
Object o = expression.resolveConstantExpressionValue();
if (o instanceof Boolean b) {
isNative = b.booleanValue();
}
}
break;
}
}
}
}
} else if (JdtDataQuerySemanticTokensProvider.isNamedQueryAnnotation(node)) {
for (Object value : node.values()) {
if (value instanceof MemberValuePair) {
MemberValuePair pair = (MemberValuePair) value;
String name = pair.getName().getFullyQualifiedName();
if (name != null) {
switch (name) {
case "query":
queryExpression = pair.getValue();
break;
}
}
}
}
EmbeddedQueryExpression q = JdtQueryVisitorUtils.extractQueryExpression(node);
if (q != null) {
Optional<Reconciler> reconcilerOpt = q.isNative() ? getSqlReconciler(project) : Optional.of(getQueryReconciler(project));
reconcilerOpt.ifPresent(r -> r.reconcile(q.query().text(), q.query().offset(), problemCollector));
}
if (queryExpression != null) {
if (isNative) {
final Expression expr = queryExpression;
getSqlReconciler(project).ifPresent(r -> reconcileExpression(r, expr, problemCollector));
} else {
reconcileExpression(getQueryReconciler(project), queryExpression, problemCollector);
}
}
return false;
return super.visit(node);
}
@Override
public boolean visit(SingleMemberAnnotation node) {
if (JdtDataQuerySemanticTokensProvider.isQueryAnnotation(node)) {
reconcileExpression(getQueryReconciler(project), node.getValue(), problemCollector);
EmbeddedQueryExpression q = JdtQueryVisitorUtils.extractQueryExpression(node);
if (q != null) {
getQueryReconciler(project).reconcile(q.query().text(), q.query().offset(), problemCollector);
}
return false;
return super.visit(node);
}
@Override
public boolean visit(MethodInvocation node) {
if ("createQuery".equals(node.getName().getIdentifier()) && node.arguments().size() <= 2 && node.arguments().get(0) instanceof Expression 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())) {
reconcileExpression(getQueryReconciler(project), queryExpr, problemCollector);
}
}
EmbeddedQueryExpression q = JdtQueryVisitorUtils.extractQueryExpression(node);
if (q != null) {
getQueryReconciler(project).reconcile(q.query().text(), q.query().offset(), problemCollector);
}
return super.visit(node);
}

View File

@@ -10,10 +10,8 @@
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.value;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -32,6 +30,7 @@ import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.LocationLink;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -80,7 +79,8 @@ public class ValueDefinitionProvider implements IJavaDefinitionProvider {
);
@Override
public List<LocationLink> getDefinitions(CancelChecker cancelToken, IJavaProject project, CompilationUnit cu, ASTNode n) {
public List<LocationLink> getDefinitions(CancelChecker cancelToken, IJavaProject project,
TextDocumentIdentifier docId, CompilationUnit cu, ASTNode n, int offset) {
if (n instanceof StringLiteral) {
StringLiteral valueNode = (StringLiteral) n;
@@ -283,6 +283,4 @@ public class ValueDefinitionProvider implements IJavaDefinitionProvider {
return resources;
}
}

View File

@@ -28,8 +28,8 @@ import org.eclipse.lsp4j.Command;
import org.eclipse.lsp4j.InlayHint;
import org.eclipse.lsp4j.InlayHintKind;
import org.eclipse.lsp4j.InlayHintLabelPart;
import org.eclipse.lsp4j.InlayHintParams;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -55,12 +55,10 @@ public class PomInlayHintHandler implements InlayHintHandler {
private static final Logger log = LoggerFactory.getLogger(PomInlayHintHandler.class);
final private SimpleLanguageServer server;
final private JavaProjectFinder projectFinder;
final private SpringProjectsProvider generationsProvider;
public PomInlayHintHandler(SimpleLanguageServer server, JavaProjectFinder projectFinder, ProjectObserver projectObserver, SpringProjectsProvider generationsProvider) {
this.server = server;
this.projectFinder = projectFinder;
this.generationsProvider = generationsProvider;
@@ -99,13 +97,13 @@ public class PomInlayHintHandler implements InlayHintHandler {
}
@Override
public List<InlayHint> handle(CancelChecker token, InlayHintParams params) {
URI uri = URI.create(params.getTextDocument().getUri());
public List<InlayHint> handle(TextDocument doc, Range range, CancelChecker cancelChecker) {
URI uri = URI.create(doc.getUri());
if ("file".equals(uri.getScheme()) && POM_XML.equals(Paths.get(uri).getFileName().toString())) {
List<InlayHintWithLazyPosition> inlayHintProviders = new ArrayList<>();
Optional<IJavaProject> projectOpt = projectFinder.find(params.getTextDocument());
Optional<IJavaProject> projectOpt = projectFinder.find(doc.getId());
if (projectOpt.isPresent()) {
IJavaProject jp = projectOpt.get();
@@ -194,20 +192,15 @@ public class PomInlayHintHandler implements InlayHintHandler {
}
if (!inlayHintProviders.isEmpty()) {
TextDocument doc = server.getTextDocumentService().getLatestSnapshot(params.getTextDocument().getUri());
if (doc != null) {
String content = doc.get();
if (!content.isEmpty()) {
// if doc is not empty, dive into the details and provide more sophisticated content assist proposals
DOMParser parser = DOMParser.getInstance();
DOMDocument dom = parser.parse(content, "", null);
String content = doc.get();
if (!content.isEmpty()) {
DOMParser parser = DOMParser.getInstance();
DOMDocument dom = parser.parse(content, "", null);
DOMElement project = dom.getDocumentElement();
DOMElement project = dom.getDocumentElement();
if (project != null && "project".equals(project.getTagName())) {
return inlayHintProviders.stream().flatMap(provider -> provider.computeInlayHints(doc, project).stream()).collect(Collectors.toList());
}
if (project != null && "project".equals(project.getTagName())) {
return inlayHintProviders.stream().flatMap(provider -> provider.computeInlayHints(doc, project).stream()).collect(Collectors.toList());
}
}
}

View File

@@ -0,0 +1,90 @@
/*******************************************************************************
* 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.nio.file.Paths;
import org.eclipse.lsp4j.Range;
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.commons.maven.java.MavenJavaProject;
import org.springframework.ide.vscode.commons.util.text.LanguageId;
import org.springframework.ide.vscode.languageserver.testharness.Editor;
import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness;
import org.springframework.ide.vscode.project.harness.ProjectsHarness;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@ExtendWith(SpringExtension.class)
@BootLanguageServerTest
@Import(HoverTestConf.class)
public class DataQueryParameterDefinitionProviderTest {
@Autowired BootLanguageServerHarness harness;
private ProjectsHarness projects = ProjectsHarness.INSTANCE;
private MavenJavaProject jp;
@BeforeEach
public void setup() throws Exception {
jp = projects.mavenProject("boot-mysql");
harness.useProject(jp);
}
@Test
void parameterNameDefinition() throws Exception {
String source = """
package my.package
import org.springframework.data.jpa.repository.Query;
public interface OwnerRepository {
@Query("SELECT DISTINCT owner FROM Owner owner left join owner.pets WHERE owner.lastName LIKE :lastName%")
Object findByLastName(@Param("lastName") String lastName);
}
""";
Editor editor = harness.newEditor(LanguageId.JAVA, source, Paths.get(jp.getLocationUri()).resolve("src/main/resource/my/package/OwnerRepository.java").toUri().toASCIIString());
Range expectedRange = editor.rangeOf("String lastName", "lastName");
Range highlightRange = editor.rangeOf(":lastName%", "lastName");
editor.assertGotoDefinition(highlightRange.getStart(), expectedRange, highlightRange);
}
@Test
void parameterOrdinalDefinition() throws Exception {
String source = """
package my.package
import org.springframework.data.jpa.repository.Query;
public interface OwnerRepository {
@Query("SELECT DISTINCT owner FROM Owner owner left join owner.pets WHERE owner.lastName LIKE :1%")
Object findByLastName(@Param("lastName") String lastName);
}
""";
Editor editor = harness.newEditor(LanguageId.JAVA, source, Paths.get(jp.getLocationUri()).resolve("src/main/resource/my/package/OwnerRepository.java").toUri().toASCIIString());
Range expectedRange = editor.rangeOf("String lastName", "lastName");
Range highlightRange = editor.rangeOf(":1%", "1");
editor.assertGotoDefinition(highlightRange.getStart(), expectedRange, highlightRange);
}
}

View File

@@ -0,0 +1,117 @@
package org.springframework.ide.vscode.boot.java.data.jpa.queries;
import java.nio.file.Paths;
import org.eclipse.lsp4j.DocumentHighlight;
import org.eclipse.lsp4j.DocumentHighlightKind;
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.commons.maven.java.MavenJavaProject;
import org.springframework.ide.vscode.commons.util.text.LanguageId;
import org.springframework.ide.vscode.languageserver.testharness.Editor;
import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness;
import org.springframework.ide.vscode.project.harness.ProjectsHarness;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@ExtendWith(SpringExtension.class)
@BootLanguageServerTest
@Import(HoverTestConf.class)
public class JdtQueryDocHighlightsProviderTest {
@Autowired BootLanguageServerHarness harness;
private ProjectsHarness projects = ProjectsHarness.INSTANCE;
private MavenJavaProject jp;
@BeforeEach
public void setup() throws Exception {
jp = projects.mavenProject("boot-mysql");
harness.useProject(jp);
}
@Test
void parameterName() throws Exception {
String source = """
package my.package
import org.springframework.data.jpa.repository.Query;
public interface OwnerRepository {
@Query("SELECT DISTINCT owner FROM Owner owner left join owner.pets WHERE owner.lastName LIKE :lastName%")
Object findByLastName(@Param("lastName") String lastName);
}
""";
Editor editor = harness.newEditor(LanguageId.JAVA, source, Paths.get(jp.getLocationUri()).resolve("src/main/resource/my/package/OwnerRepository.java").toUri().toASCIIString());
editor.assertDocumentHighlights(":lastName", new DocumentHighlight(editor.rangeOf("String lastName", "lastName"), DocumentHighlightKind.Write));
}
@Test
void parameterOrdinal() throws Exception {
String source = """
package my.package
import org.springframework.data.jpa.repository.Query;
public interface OwnerRepository {
@Query("SELECT DISTINCT owner FROM Owner owner left join owner.pets WHERE owner.lastName LIKE :1%")
Object findByLastName(@Param("lastName") String lastName);
}
""";
Editor editor = harness.newEditor(LanguageId.JAVA, source, Paths.get(jp.getLocationUri()).resolve("src/main/resource/my/package/OwnerRepository.java").toUri().toASCIIString());
editor.assertDocumentHighlights(":1", new DocumentHighlight(editor.rangeOf("String lastName", "lastName"), DocumentHighlightKind.Write));
}
@Test
void noOrdinalMatch() throws Exception {
String source = """
package my.package
import org.springframework.data.jpa.repository.Query;
public interface OwnerRepository {
@Query("SELECT DISTINCT owner FROM Owner owner left join owner.pets WHERE owner.lastName LIKE :2%")
Object findByLastName(@Param("lastName") String lastName);
}
""";
Editor editor = harness.newEditor(LanguageId.JAVA, source, Paths.get(jp.getLocationUri()).resolve("src/main/resource/my/package/OwnerRepository.java").toUri().toASCIIString());
editor.assertDocumentHighlights(":1");
}
@Test
void noParameterNameMatch() throws Exception {
String source = """
package my.package
import org.springframework.data.jpa.repository.Query;
public interface OwnerRepository {
@Query("SELECT DISTINCT owner FROM Owner owner left join owner.pets WHERE owner.lastName LIKE :name%")
Object findByLastName(@Param("lastName") String lastName);
}
""";
Editor editor = harness.newEditor(LanguageId.JAVA, source, Paths.get(jp.getLocationUri()).resolve("src/main/resource/my/package/OwnerRepository.java").toUri().toASCIIString());
editor.assertDocumentHighlights(":name");
}
}

View File

@@ -28,9 +28,7 @@ import java.util.Optional;
import org.eclipse.lsp4j.Command;
import org.eclipse.lsp4j.InlayHint;
import org.eclipse.lsp4j.InlayHintLabelPart;
import org.eclipse.lsp4j.InlayHintParams;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
import org.junit.jupiter.api.Test;
import org.springframework.ide.vscode.boot.validation.generations.SpringProjectsProvider;
@@ -79,7 +77,7 @@ public class PomInlayHintHandlerTest {
PomInlayHintHandler inlayHanlder = new PomInlayHintHandler(server, projectFinder, ProjectObserver.NULL, projectProvider);
List<InlayHint> hints = inlayHanlder.handle(mock(CancelChecker.class), new InlayHintParams(new TextDocumentIdentifier(doc.getUri()), doc.toRange(0, doc.getLength())));
List<InlayHint> hints = inlayHanlder.handle(doc, doc.toRange(0, doc.getLength()), mock(CancelChecker.class));
assertEquals(1, hints.size());
@@ -131,7 +129,7 @@ public class PomInlayHintHandlerTest {
PomInlayHintHandler inlayHanlder = new PomInlayHintHandler(server, projectFinder, ProjectObserver.NULL, projectProvider);
List<InlayHint> hints = inlayHanlder.handle(mock(CancelChecker.class), new InlayHintParams(new TextDocumentIdentifier(doc.getUri()), doc.toRange(0, doc.getLength())));
List<InlayHint> hints = inlayHanlder.handle(doc, doc.toRange(0, doc.getLength()), mock(CancelChecker.class));
assertEquals(0, hints.size());
@@ -169,7 +167,7 @@ public class PomInlayHintHandlerTest {
PomInlayHintHandler inlayHanlder = new PomInlayHintHandler(server, projectFinder, ProjectObserver.NULL, projectProvider);
List<InlayHint> hints = inlayHanlder.handle(mock(CancelChecker.class), new InlayHintParams(new TextDocumentIdentifier(doc.getUri()), doc.toRange(0, doc.getLength())));
List<InlayHint> hints = inlayHanlder.handle(doc, doc.toRange(0, doc.getLength()), mock(CancelChecker.class));
assertEquals(1, hints.size());
@@ -225,7 +223,7 @@ public class PomInlayHintHandlerTest {
PomInlayHintHandler inlayHanlder = new PomInlayHintHandler(server, projectFinder, ProjectObserver.NULL, projectProvider);
List<InlayHint> hints = inlayHanlder.handle(mock(CancelChecker.class), new InlayHintParams(new TextDocumentIdentifier(doc.getUri()), doc.toRange(0, doc.getLength())));
List<InlayHint> hints = inlayHanlder.handle(doc, doc.toRange(0, doc.getLength()), mock(CancelChecker.class));
assertEquals(0, hints.size());
}