Quickfix sorting. Preferred fix. Wording for RequestMapping quickfixes

This commit is contained in:
aboyko
2023-09-26 22:07:19 -04:00
parent e41f45dc8f
commit d1e9ed51ea
10 changed files with 183 additions and 20 deletions

View File

@@ -10,6 +10,7 @@
id="org.eclipse.languageserver.languages.springboot"
label="Spring Boot Language Server"
lastDocumentDisconnectedTimeout="2147483647"
markerType="org.springframework.tooling.boot.ls.diagnostic"
serverInterface="org.springframework.ide.vscode.commons.protocol.spring.SpringIndexLanguageServer"
singleton="true">
</server>
@@ -752,6 +753,21 @@
class="org.springframework.tooling.boot.ls.Startup">
</startup>
</extension>
<extension
point="org.eclipse.ui.ide.markerResolution">
<markerResolutionGenerator
class="org.springframework.tooling.boot.ls.markers.LSPBootCodeActionMarkerResolution"
markerType="org.springframework.tooling.boot.ls.diagnostic">
</markerResolutionGenerator>
</extension>
<extension
id="diagnostic"
name="Spring Boot"
point="org.eclipse.core.resources.markers">
<super
type="org.eclipse.core.resources.problemmarker">
</super>
</extension>
<!--
<extension
point="org.eclipse.wildwebdeveloper.xml.lemminxExtension">

View File

@@ -17,6 +17,7 @@ import java.util.List;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.jface.bindings.Binding;
import org.eclipse.jface.resource.ImageRegistry;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.keys.IBindingService;
import org.eclipse.ui.plugin.AbstractUIPlugin;
@@ -32,6 +33,8 @@ import org.springframework.tooling.boot.ls.prefs.LiveInformationPreferencePage;
*/
public class BootLanguageServerPlugin extends AbstractUIPlugin {
public static final String SPRING_ICON = "SPRING_ICON";
public static String PLUGIN_ID = "org.springframework.tooling.boot.ls";
private static final Object LSP4E_COMMAND_SYMBOL_IN_WORKSPACE = "org.eclipse.lsp4e.symbolinworkspace";
@@ -108,4 +111,12 @@ public class BootLanguageServerPlugin extends AbstractUIPlugin {
});
}
}
@Override
protected void initializeImageRegistry(ImageRegistry reg) {
super.initializeImageRegistry(reg);
reg.put(SPRING_ICON, imageDescriptorFromPlugin(PLUGIN_ID, "icons/spring_obj.gif"));
}
}

View File

@@ -0,0 +1,84 @@
/*******************************************************************************
* Copyright (c) 2023 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
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* VMware, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.tooling.boot.ls.markers;
import org.eclipse.core.resources.IMarker;
import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.ui.IMarkerResolution;
import org.eclipse.ui.IMarkerResolutionRelevance;
import org.springframework.tooling.boot.ls.BootLanguageServerPlugin;
public class DelegateMarkerResolution implements IMarkerResolution, IMarkerResolutionRelevance, IJavaCompletionProposal {
final private IMarkerResolution delegate;
final private int relevance;
public DelegateMarkerResolution(IMarkerResolution res, int relevance) {
this.delegate = res;
this.relevance = relevance;
}
@Override
public String getLabel() {
return delegate.getLabel();
}
@Override
public void run(IMarker marker) {
delegate.run(marker);
}
@Override
public void apply(IDocument document) {
throw new UnsupportedOperationException();
}
@Override
public Point getSelection(IDocument document) {
throw new UnsupportedOperationException();
}
@Override
public String getAdditionalProposalInfo() {
throw new UnsupportedOperationException();
}
@Override
public String getDisplayString() {
return getLabel();
}
@Override
public Image getImage() {
return BootLanguageServerPlugin.getDefault().getImageRegistry().get(BootLanguageServerPlugin.SPRING_ICON);
}
@Override
public IContextInformation getContextInformation() {
throw new UnsupportedOperationException();
}
@Override
public int getRelevance() {
return relevance;
}
@Override
public int getRelevanceForResolution() {
return relevance;
}
}

View File

@@ -0,0 +1,30 @@
/*******************************************************************************
* Copyright (c) 2023 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
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* VMware, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.tooling.boot.ls.markers;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.core.resources.IMarker;
import org.eclipse.lsp4e.operations.codeactions.LSPCodeActionMarkerResolution;
import org.eclipse.ui.IMarkerResolution;
public class LSPBootCodeActionMarkerResolution extends LSPCodeActionMarkerResolution {
@Override
public IMarkerResolution[] getResolutions(IMarker marker) {
IMarkerResolution[] res = super.getResolutions(marker);
AtomicInteger relevance = new AtomicInteger(res.length);
return Arrays.stream(res).map(r -> new DelegateMarkerResolution(r, relevance.getAndDecrement()))
.toArray(IMarkerResolution[]::new);
}
}

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2017 Pivotal, Inc.
* Copyright (c) 2017, 2023 Pivotal, 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
@@ -30,13 +30,20 @@ public class Quickfix<T> {
public final QuickfixType type;
public final T params;
public final String title;
public final boolean preferred;
public QuickfixData(QuickfixType type, T params, String title) {
public QuickfixData(QuickfixType type, T params, String title, boolean preferred) {
super();
this.type = type;
this.params = params;
this.title = title;
this.preferred = preferred;
}
public QuickfixData(QuickfixType type, T params, String title) {
this(type, params, title, false);
}
}
private final String CODE_ACTION_CMD_ID;

View File

@@ -804,6 +804,7 @@ public final class SimpleLanguageServer implements Sts4LanguageServer, SpringInd
CodeAction ca = new CodeAction();
ca.setKind(CodeActionKind.QuickFix);
ca.setTitle(fix.title);
ca.setIsPreferred(fix.preferred);
ca.setDiagnostics(List.of(refDiagnostic));
ca.setCommand(new Command(
fix.title,

View File

@@ -32,6 +32,8 @@ final public class FixDescriptor {
private String[] typeStubs;
private boolean preferred;
public FixDescriptor(String recipeId, List<String> docUris, String label) {
this.recipeId = recipeId;
this.docUris = docUris;
@@ -59,6 +61,11 @@ final public class FixDescriptor {
return this;
}
public FixDescriptor withPreferred(boolean preferred) {
this.preferred = preferred;
return this;
}
public String getRecipeId() {
return recipeId;
}
@@ -86,5 +93,9 @@ final public class FixDescriptor {
public String[] getTypeStubs() {
return typeStubs;
}
public boolean isPreferred() {
return preferred;
}
}

View File

@@ -13,6 +13,7 @@ package org.springframework.ide.vscode.boot.java.reconcilers;
import static org.springframework.ide.vscode.commons.java.SpringProjectUtil.springBootVersionGreaterOrEqual;
import java.net.URI;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -45,11 +46,11 @@ import org.springframework.ide.vscode.commons.rewrite.java.NoRequestMappingAnnot
public class NoRequestMappingAnnotationReconciler implements JdtAstReconciler {
private static final String PROBLEM_LABEL = "Replace with precise mapping annotation";
private static final String UNSUPPORTED_REQUEST_METHOD = "UNSUPPORTED";
private static final List<String> SUPPORTED_REQUEST_METHODS = List.of("GET", "POST", "PUT", "DELETE", "PATCH");
private static final String LABEL = "Replace @RequestMapping with specific @GetMapping, @PostMapping etc.";
private static final List<String> SUPPORTED_REQUEST_METHODS = List.of("GET", "POST", "PUT", "DELETE", "PATCH");
private QuickfixRegistry registry;
@@ -87,17 +88,19 @@ public class NoRequestMappingAnnotationReconciler implements JdtAstReconciler {
String uri = docUri.toASCIIString();
Range range = ReconcileUtils.createOpenRewriteRange(cu, a);
if (requestMethods.size() == 1 && SUPPORTED_REQUEST_METHODS.contains(requestMethods.get(0))) {
ReconcileProblemImpl problem = new ReconcileProblemImpl(getProblemType(), LABEL, a.getStartPosition(), a.getLength());
ReconcileProblemImpl problem = new ReconcileProblemImpl(getProblemType(), PROBLEM_LABEL, a.getStartPosition(), a.getLength());
ReconcileUtils.setRewriteFixes(registry, problem, List.of(
createFixDescriptor(uri, range, requestMethods.get(0)),
new FixDescriptor(org.openrewrite.java.spring.NoRequestMappingAnnotation.class.getName(), List.of(uri), ReconcileUtils.buildLabel(LABEL, RecipeScope.FILE))
createFixDescriptor(uri, range, requestMethods.get(0)).withPreferred(true),
new FixDescriptor(org.openrewrite.java.spring.NoRequestMappingAnnotation.class.getName(), List.of(uri),
"Replace all `@RequestMapping` in file '%s' with `@GetMapping`, `@PostMapping`, etc.".formatted(Paths.get(docUri).getFileName().toString()))
.withRecipeScope(RecipeScope.FILE),
new FixDescriptor(org.openrewrite.java.spring.NoRequestMappingAnnotation.class.getName(), List.of(uri), ReconcileUtils.buildLabel(LABEL, RecipeScope.PROJECT))
new FixDescriptor(org.openrewrite.java.spring.NoRequestMappingAnnotation.class.getName(), List.of(uri),
"Replace all `@RequestMapping` in project '%s' with `@GetMapping`, `@PostMapping`, etc.".formatted(project.getElementName()))
.withRecipeScope(RecipeScope.PROJECT)
));
problemCollector.accept(problem);
} else if (SUPPORTED_REQUEST_METHODS == requestMethods) { // the case of no request methods specified
ReconcileProblemImpl problem = new ReconcileProblemImpl(getProblemType(), "Consider replacing with precise mapping annotation", a.getStartPosition(), a.getLength());
ReconcileProblemImpl problem = new ReconcileProblemImpl(getProblemType(), PROBLEM_LABEL, a.getStartPosition(), a.getLength());
ReconcileUtils.setRewriteFixes(registry, problem,
requestMethods.stream().map(m -> createFixDescriptor(uri, range, m)).collect(Collectors.toList()));
problemCollector.accept(problem);
@@ -110,7 +113,7 @@ public class NoRequestMappingAnnotationReconciler implements JdtAstReconciler {
}
private static FixDescriptor createFixDescriptor(String uri, Range range, String requestMethod) {
return new FixDescriptor(NoRequestMappingAnnotation.class.getName(), List.of(uri), "Replace with '@%s'".formatted(NoRequestMappingAnnotation.associatedRequestMapping(requestMethod)))
return new FixDescriptor(NoRequestMappingAnnotation.class.getName(), List.of(uri), "Replace with `@%s`".formatted(NoRequestMappingAnnotation.associatedRequestMapping(requestMethod)))
.withRangeScope(range)
.withParameters(Map.of("preferredMapping", requestMethod))
.withRecipeScope(RecipeScope.NODE);

View File

@@ -59,7 +59,7 @@ public class ReconcileUtils {
public static void setRewriteFixes(QuickfixRegistry registry, ReconcileProblemImpl problem, Collection<FixDescriptor> fixDescritptors) {
QuickfixType quickFixType = getRewriteQuickFixType(registry);
for (FixDescriptor f : fixDescritptors) {
problem.addQuickfix(new QuickfixData<>(quickFixType, f, f.getLabel()));
problem.addQuickfix(new QuickfixData<>(quickFixType, f, f.getLabel(), f.isPreferred()));
}
}

View File

@@ -81,7 +81,7 @@ public class NoRequestMappingAnnotationReconcilerTest extends BaseReconcilerTest
assertEquals("@RequestMapping(value = \"/1\", method = RequestMethod.GET)", markedStr);
assertEquals(3, problem.getQuickfixes().size());
assertEquals("Replace with '@GetMapping'", problems.get(0).getQuickfixes().get(0).title);
assertEquals("Replace with `@GetMapping`", problems.get(0).getQuickfixes().get(0).title);
}
@@ -116,7 +116,7 @@ public class NoRequestMappingAnnotationReconcilerTest extends BaseReconcilerTest
assertEquals("@RequestMapping(value = \"/1\", method = GET)", markedStr);
assertEquals(3, problem.getQuickfixes().size());
assertEquals("Replace with '@GetMapping'", problems.get(0).getQuickfixes().get(0).title);
assertEquals("Replace with `@GetMapping`", problems.get(0).getQuickfixes().get(0).title);
}
@@ -151,7 +151,7 @@ public class NoRequestMappingAnnotationReconcilerTest extends BaseReconcilerTest
assertEquals("@RequestMapping(value = \"/1\", method = { RequestMethod.GET })", markedStr);
assertEquals(3, problem.getQuickfixes().size());
assertEquals("Replace with '@GetMapping'", problems.get(0).getQuickfixes().get(0).title);
assertEquals("Replace with `@GetMapping`", problems.get(0).getQuickfixes().get(0).title);
}
@@ -231,11 +231,11 @@ public class NoRequestMappingAnnotationReconcilerTest extends BaseReconcilerTest
assertEquals(1, problems.size());
assertEquals(5, problems.get(0).getQuickfixes().size());
assertEquals("Replace with '@GetMapping'", problems.get(0).getQuickfixes().get(0).title);
assertEquals("Replace with '@PostMapping'", problems.get(0).getQuickfixes().get(1).title);
assertEquals("Replace with '@PutMapping'", problems.get(0).getQuickfixes().get(2).title);
assertEquals("Replace with '@DeleteMapping'", problems.get(0).getQuickfixes().get(3).title);
assertEquals("Replace with '@PatchMapping'", problems.get(0).getQuickfixes().get(4).title);
assertEquals("Replace with `@GetMapping`", problems.get(0).getQuickfixes().get(0).title);
assertEquals("Replace with `@PostMapping`", problems.get(0).getQuickfixes().get(1).title);
assertEquals("Replace with `@PutMapping`", problems.get(0).getQuickfixes().get(2).title);
assertEquals("Replace with `@DeleteMapping`", problems.get(0).getQuickfixes().get(3).title);
assertEquals("Replace with `@PatchMapping`", problems.get(0).getQuickfixes().get(4).title);
}
@Test