InlayHint to upgrade spring-boot-starter-parent to the latest patch
This commit is contained in:
@@ -12,21 +12,28 @@ package org.springframework.ide.vscode.boot.maven;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.lemminx.dom.DOMDocument;
|
||||
import org.eclipse.lemminx.dom.DOMElement;
|
||||
import org.eclipse.lemminx.dom.DOMParser;
|
||||
import org.eclipse.lemminx.dom.DOMText;
|
||||
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.jsonrpc.CancelChecker;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.SpringBootUpgrade;
|
||||
import org.springframework.ide.vscode.boot.validation.generations.GenerationsValidator;
|
||||
import org.springframework.ide.vscode.boot.validation.generations.SpringProjectsProvider;
|
||||
import org.springframework.ide.vscode.boot.validation.generations.VersionValidationUtils;
|
||||
@@ -39,6 +46,7 @@ import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFin
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.InlayHintHandler;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.util.BadLocationException;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
public class PomInlayHintHandler implements InlayHintHandler {
|
||||
@@ -95,72 +103,143 @@ public class PomInlayHintHandler implements InlayHintHandler {
|
||||
URI uri = URI.create(params.getTextDocument().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());
|
||||
if (projectOpt.isPresent()) {
|
||||
|
||||
URI buildFileUri = projectOpt.get().getProjectBuild().getBuildFile();
|
||||
|
||||
if (buildFileUri.equals(uri) && SpringProjectUtil.isBootProject(projectOpt.get())) {
|
||||
Version currentVersion = SpringProjectUtil.getSpringBootVersion(projectOpt.get());
|
||||
IJavaProject jp = projectOpt.get();
|
||||
URI buildFileUri = jp.getProjectBuild().getBuildFile();
|
||||
|
||||
if (buildFileUri.equals(uri) && SpringProjectUtil.isBootProject(jp)) {
|
||||
Version currentVersion = SpringProjectUtil.getSpringBootVersion(jp);
|
||||
if (currentVersion != null) {
|
||||
try {
|
||||
ResolvedSpringProject genProject = generationsProvider.getProject(SpringProjectUtil.SPRING_BOOT);
|
||||
if (genProject != null) {
|
||||
Generation generation = GenerationsValidator.getGenerationForJavaProject(projectOpt.get(), genProject);
|
||||
if (generation != null && VersionValidationUtils.isOssValid(generation)) {
|
||||
TextDocument doc = server.getTextDocumentService().getLatestSnapshot(params.getTextDocument().getUri());
|
||||
|
||||
if (doc != null) {
|
||||
String content = doc.get();
|
||||
Version latestPatch = VersionValidationUtils.getNewerLatestPatchRelease(genProject.getReleases(), currentVersion);
|
||||
if (latestPatch != null) {
|
||||
inlayHintProviders.add(new InlayHintWithLazyPosition(() -> {
|
||||
Command command = new Command();
|
||||
command.setTitle("Upgrade to the Latest Patch");
|
||||
command.setCommand(SpringBootUpgrade.CMD_UPGRADE_SPRING_BOOT);
|
||||
command.setArguments(List.of(jp.getLocationUri().toASCIIString(), latestPatch.toString(), false));
|
||||
|
||||
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);
|
||||
|
||||
DOMElement project = dom.getDocumentElement();
|
||||
if (project != null && "project".equals(project.getTagName())) {
|
||||
for (int j = 0; j < project.getChildNodes().getLength(); j++) {
|
||||
var child = project.getChildNodes().item(j);
|
||||
if ("dependencies".equals(child.getNodeName()) && child instanceof DOMElement dependencies) {
|
||||
try {
|
||||
Command command = new Command();
|
||||
command.setTitle("Add Spring Boot Starters");
|
||||
command.setCommand("spring.initializr.addStarters");
|
||||
|
||||
InlayHintLabelPart label = new InlayHintLabelPart("Add Spring Boot Starters...");
|
||||
label.setCommand(command);
|
||||
|
||||
InlayHint hint = new InlayHint();
|
||||
hint.setPosition(doc.toPosition(dependencies.getStartTagCloseOffset() + 1));
|
||||
hint.setKind(InlayHintKind.Parameter);
|
||||
hint.setPaddingLeft(true);
|
||||
hint.setLabel(List.of(label));
|
||||
|
||||
return List.of(hint);
|
||||
} catch (Exception e) {
|
||||
log.error("", e);
|
||||
break;
|
||||
}
|
||||
InlayHintLabelPart label = new InlayHintLabelPart("Upgrade to the Latest Patch");
|
||||
label.setCommand(command);
|
||||
|
||||
InlayHint hint = new InlayHint();
|
||||
hint.setKind(InlayHintKind.Parameter);
|
||||
hint.setPaddingLeft(true);
|
||||
hint.setLabel(List.of(label));
|
||||
return hint;
|
||||
}, (d, e) -> {
|
||||
Optional<String> parentArtifactIdOpt = findChildElement(e, 0, "parent", "artifactId")
|
||||
.flatMap(PomInlayHintHandler::getNodeValue);
|
||||
if (parentArtifactIdOpt.isPresent() && "spring-boot-starter-parent".equals(parentArtifactIdOpt.get())) {
|
||||
Optional<DOMElement> parentVersionOpt = findChildElement(e, 0, "parent", "version");
|
||||
if (parentVersionOpt.isPresent()) {
|
||||
DOMElement parentVersion = parentVersionOpt.get();
|
||||
// Get the current version in the POM in case file is not saved
|
||||
Optional<Version> parentVersionValueOpt = getNodeValue(parentVersion).flatMap(s -> Optional.ofNullable(Version.parse(s)));
|
||||
if (parentVersionValueOpt.isPresent() && parentVersionValueOpt.get().compareTo(latestPatch) < 0) {
|
||||
try {
|
||||
return List.of(d.toPosition(parentVersion.getEndTagCloseOffset() + 1));
|
||||
} catch (Exception ex) {
|
||||
log.error("", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}));
|
||||
}
|
||||
Generation generation = GenerationsValidator.getGenerationForJavaProject(jp, genProject);
|
||||
if (generation != null && VersionValidationUtils.isOssValid(generation)) {
|
||||
|
||||
inlayHintProviders.add(new InlayHintWithLazyPosition(() -> {
|
||||
Command command = new Command();
|
||||
command.setTitle("Add Spring Boot Starters");
|
||||
command.setCommand("spring.initializr.addStarters");
|
||||
|
||||
InlayHintLabelPart label = new InlayHintLabelPart("Add Spring Boot Starters...");
|
||||
label.setCommand(command);
|
||||
|
||||
InlayHint hint = new InlayHint();
|
||||
hint.setKind(InlayHintKind.Parameter);
|
||||
hint.setPaddingLeft(true);
|
||||
hint.setLabel(List.of(label));
|
||||
return hint;
|
||||
}, (d, e) -> {
|
||||
Optional<DOMElement> dependenciesOpt = findChildElement(e, 0, "dependencies");
|
||||
if (dependenciesOpt.isPresent()) {
|
||||
DOMElement dependencies = dependenciesOpt.get();
|
||||
try {
|
||||
return List.of(d.toPosition(dependencies.getStartTagCloseOffset() + 1));
|
||||
} catch (BadLocationException ex) {
|
||||
log.error("", ex);
|
||||
}
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}));
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
DOMElement project = dom.getDocumentElement();
|
||||
|
||||
if (project != null && "project".equals(project.getTagName())) {
|
||||
return inlayHintProviders.stream().flatMap(provider -> provider.computeInlayHints(doc, project).stream()).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private static Optional<DOMElement> findChildElement(DOMElement e, int idx, String... tagPath) {
|
||||
if (tagPath.length == idx) {
|
||||
return Optional.of(e);
|
||||
}
|
||||
for (int j = 0; j < e.getChildNodes().getLength(); j++) {
|
||||
var child = e.getChildNodes().item(j);
|
||||
if (tagPath[idx].equals(child.getNodeName()) && child instanceof DOMElement c) {
|
||||
return findChildElement(c, idx + 1, tagPath);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static Optional<String> getNodeValue(DOMElement e) {
|
||||
return e.getChildren().stream().filter(DOMText.class::isInstance).map(DOMText.class::cast).map(tn -> tn.getData().trim()).findFirst();
|
||||
}
|
||||
|
||||
private record InlayHintWithLazyPosition(Supplier<InlayHint> hintFactory, BiFunction<TextDocument, DOMElement, List<Position>> positionSupplier) {
|
||||
List<InlayHint> computeInlayHints(TextDocument d, DOMElement e) {
|
||||
return positionSupplier.apply(d, e).stream().map(p -> {
|
||||
InlayHint hint = hintFactory.get();
|
||||
hint.setPosition(p);
|
||||
return hint;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ import org.junit.jupiter.api.Test;
|
||||
import org.springframework.ide.vscode.boot.validation.generations.SpringProjectsProvider;
|
||||
import org.springframework.ide.vscode.boot.validation.generations.json.Generation;
|
||||
import org.springframework.ide.vscode.boot.validation.generations.json.ResolvedSpringProject;
|
||||
import org.springframework.ide.vscode.commons.Version;
|
||||
import org.springframework.ide.vscode.commons.java.SpringProjectUtil;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver;
|
||||
@@ -135,5 +136,98 @@ public class PomInlayHintHandlerTest {
|
||||
assertEquals(0, hints.size());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void upgradePatchVersionInlay() throws Exception {
|
||||
MavenJavaProject jp = projects.mavenProject("empty-boot-15-web-app");
|
||||
|
||||
TextDocument doc = new TextDocument(jp.getProjectBuild().getBuildFile().toASCIIString(), LanguageId.XML, 0, Files.readString(Paths.get(jp.getProjectBuild().getBuildFile())));
|
||||
|
||||
JavaProjectFinder projectFinder = mock(JavaProjectFinder.class);
|
||||
when(projectFinder.find(any())).thenReturn(Optional.of(jp));
|
||||
|
||||
SimpleTextDocumentService documents = mock(SimpleTextDocumentService.class);
|
||||
when(documents.getLatestSnapshot(anyString())).thenReturn(doc);
|
||||
|
||||
SimpleLanguageServer server = mock(SimpleLanguageServer.class);
|
||||
when(server.getTextDocumentService()).thenReturn(documents);
|
||||
|
||||
ResolvedSpringProject resolvedProject = mock(ResolvedSpringProject.class);
|
||||
when(resolvedProject.getGenerations()).thenReturn(null);
|
||||
when(resolvedProject.getSlug()).thenReturn(SpringProjectUtil.SPRING_BOOT);
|
||||
when(resolvedProject.getReleases()).thenReturn(List.of(
|
||||
Version.parse("1.5.6"),
|
||||
Version.parse("1.5.7"),
|
||||
Version.parse("1.5.8"),
|
||||
Version.parse("1.5.9"),
|
||||
Version.parse("1.5.10"),
|
||||
Version.parse("2.0.0")
|
||||
));
|
||||
|
||||
SpringProjectsProvider projectProvider = mock(SpringProjectsProvider.class);
|
||||
when(projectProvider.getProject(SpringProjectUtil.SPRING_BOOT)).thenReturn(resolvedProject);
|
||||
|
||||
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())));
|
||||
|
||||
assertEquals(1, hints.size());
|
||||
|
||||
InlayHint hint = hints.get(0);
|
||||
|
||||
assertEquals(new Position(17, 34), hint.getPosition());
|
||||
|
||||
assertTrue(hint.getLabel().isRight());
|
||||
|
||||
assertEquals(1, hint.getLabel().getRight().size());
|
||||
|
||||
InlayHintLabelPart labelPart = hint.getLabel().getRight().get(0);
|
||||
|
||||
assertEquals("Upgrade to the Latest Patch", labelPart.getValue());
|
||||
|
||||
Command cmd = labelPart.getCommand();
|
||||
|
||||
assertNotNull(cmd);
|
||||
|
||||
assertEquals("sts/upgrade/spring-boot", cmd.getCommand());
|
||||
assertEquals(jp.getLocationUri().toASCIIString(), cmd.getArguments().get(0));
|
||||
assertEquals("1.5.10", cmd.getArguments().get(1));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void upgradePatchVersionInlay_AlreadyOnLatestPatch() throws Exception {
|
||||
MavenJavaProject jp = projects.mavenProject("empty-boot-15-web-app");
|
||||
|
||||
TextDocument doc = new TextDocument(jp.getProjectBuild().getBuildFile().toASCIIString(), LanguageId.XML, 0, Files.readString(Paths.get(jp.getProjectBuild().getBuildFile())));
|
||||
|
||||
JavaProjectFinder projectFinder = mock(JavaProjectFinder.class);
|
||||
when(projectFinder.find(any())).thenReturn(Optional.of(jp));
|
||||
|
||||
SimpleTextDocumentService documents = mock(SimpleTextDocumentService.class);
|
||||
when(documents.getLatestSnapshot(anyString())).thenReturn(doc);
|
||||
|
||||
SimpleLanguageServer server = mock(SimpleLanguageServer.class);
|
||||
when(server.getTextDocumentService()).thenReturn(documents);
|
||||
|
||||
ResolvedSpringProject resolvedProject = mock(ResolvedSpringProject.class);
|
||||
when(resolvedProject.getGenerations()).thenReturn(null);
|
||||
when(resolvedProject.getSlug()).thenReturn(SpringProjectUtil.SPRING_BOOT);
|
||||
when(resolvedProject.getReleases()).thenReturn(List.of(
|
||||
Version.parse("1.5.6"),
|
||||
Version.parse("1.5.7"),
|
||||
Version.parse("1.5.8"),
|
||||
Version.parse("2.0.0")
|
||||
));
|
||||
|
||||
SpringProjectsProvider projectProvider = mock(SpringProjectsProvider.class);
|
||||
when(projectProvider.getProject(SpringProjectUtil.SPRING_BOOT)).thenReturn(resolvedProject);
|
||||
|
||||
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())));
|
||||
|
||||
assertEquals(0, hints.size());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user