Skip to content

Commit 25ae118

Browse files
Issue 1401: QuickFix for ServerEndpoint paths without a leading slash (#1412)
* Update Error code and test case * PrefixSlash quickfix draft * Prefix Slash proposal implementation * Copyright * Testcase update for PrefixSlashAnnotationQuickFix * Refactor performUpdate * Copyright Updates
1 parent ccf8790 commit 25ae118

File tree

7 files changed

+165
-6
lines changed

7 files changed

+165
-6
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 IBM Corporation and others.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v. 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*
10+
* Contributors:
11+
* IBM Corporation - initial API and implementation
12+
*******************************************************************************/
13+
package io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.codeAction.proposal;
14+
15+
import com.intellij.openapi.project.Project;
16+
import com.intellij.psi.*;
17+
import io.openliberty.tools.intellij.lsp4mp4ij.psi.core.java.corrections.proposal.ASTRewriteCorrectionProposal;
18+
import org.eclipse.lsp4j.CodeActionKind;
19+
20+
public class PrefixSlashAnnotationProposal extends ASTRewriteCorrectionProposal {
21+
private final PsiAnnotation fAnnotation;
22+
23+
public PrefixSlashAnnotationProposal(String name, int relevance, PsiFile sourceCU, PsiElement declaringNode, PsiAnnotation annotationNode) {
24+
super(name, CodeActionKind.QuickFix, declaringNode, relevance, sourceCU);
25+
this.fAnnotation = annotationNode;
26+
}
27+
28+
@Override
29+
public void performUpdate() {
30+
final String FORWARD_SLASH = "/";
31+
final String ESCAPE_QUOTE = "\"";
32+
if (fAnnotation != null) {
33+
PsiElementFactory factory = JavaPsiFacade.getElementFactory(fAnnotation.getProject());
34+
for (PsiNameValuePair pair : fAnnotation.getParameterList().getAttributes()) {
35+
if (pair.getValue() instanceof PsiLiteralExpression literal && literal.getValue() instanceof String valueText) {
36+
if (!valueText.startsWith(FORWARD_SLASH)) {
37+
fAnnotation.setDeclaredAttributeValue(PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME, factory.createExpressionFromText(
38+
String.format("%s%s%s%s", ESCAPE_QUOTE, FORWARD_SLASH, valueText, ESCAPE_QUOTE),
39+
fAnnotation
40+
));
41+
}
42+
}
43+
}
44+
}
45+
}
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 IBM Corporation and others.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v. 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*
10+
* Contributors:
11+
* IBM Corporation - initial API and implementation
12+
*******************************************************************************/
13+
package io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.codeAction.proposal.quickfix;
14+
15+
import com.intellij.psi.*;
16+
import com.intellij.psi.util.PsiTreeUtil;
17+
import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.JDTUtils;
18+
import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.Messages;
19+
import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.codeAction.proposal.PrefixSlashAnnotationProposal;
20+
import io.openliberty.tools.intellij.lsp4mp4ij.psi.core.java.codeaction.IJavaCodeActionParticipant;
21+
import io.openliberty.tools.intellij.lsp4mp4ij.psi.core.java.codeaction.JavaCodeActionContext;
22+
import io.openliberty.tools.intellij.lsp4mp4ij.psi.core.java.codeaction.JavaCodeActionResolveContext;
23+
import io.openliberty.tools.intellij.lsp4mp4ij.psi.core.java.corrections.proposal.ChangeCorrectionProposal;
24+
import io.openliberty.tools.intellij.util.ExceptionUtil;
25+
import org.eclipse.lsp4j.CodeAction;
26+
import org.eclipse.lsp4j.Diagnostic;
27+
import org.eclipse.lsp4mp.commons.codeaction.CodeActionResolveData;
28+
29+
import java.util.ArrayList;
30+
import java.util.Collections;
31+
import java.util.List;
32+
import java.util.logging.Logger;
33+
34+
public class PrefixSlashAnnotationQuickFix implements IJavaCodeActionParticipant {
35+
36+
private static final Logger LOGGER = Logger.getLogger(PrefixSlashAnnotationQuickFix.class.getName());
37+
38+
/**
39+
* Returns the unique identifier of this code action participant.
40+
*
41+
* @return the unique identifier of this code action participant
42+
*/
43+
@Override
44+
public String getParticipantId() {
45+
return PrefixSlashAnnotationQuickFix.class.getName();
46+
}
47+
48+
/**
49+
* Return the code action list for a given compilation unit and null otherwise.
50+
*
51+
* @param context the java code action context.
52+
* @param diagnostic the diagnostic which must be fixed and null otherwise.
53+
* @return the code action list for a given compilation unit and null otherwise.
54+
*/
55+
@Override
56+
public List<? extends CodeAction> getCodeActions(JavaCodeActionContext context, Diagnostic diagnostic) {
57+
PsiElement node = context.getCoveredNode();
58+
PsiElement parentType = getBinding(node);
59+
if (parentType != null) {
60+
List<CodeAction> codeActions = new ArrayList<>();
61+
codeActions.add(JDTUtils.createCodeAction(context, diagnostic, getLabel(), getParticipantId()));
62+
return codeActions;
63+
}
64+
return Collections.emptyList();
65+
}
66+
67+
/**
68+
* Returns the code action with the TextEdits filled in.
69+
*
70+
* @param context the code action context to resolve
71+
* @return the code action with the TextEdits filled in
72+
*/
73+
@Override
74+
public CodeAction resolveCodeAction(JavaCodeActionResolveContext context) {
75+
final CodeAction toResolve = context.getUnresolved();
76+
final PsiElement node = context.getCoveredNode();
77+
PsiElement declaringNode = getBinding(context.getCoveredNode());
78+
PsiAnnotation annotationNode = PsiTreeUtil.getParentOfType(node, PsiAnnotation.class);
79+
String label = getLabel();
80+
ChangeCorrectionProposal proposal = new PrefixSlashAnnotationProposal(label, 0, context.getSource().getCompilationUnit(), declaringNode, annotationNode);
81+
ExceptionUtil.executeWithWorkspaceEditHandling(context, proposal, toResolve, LOGGER, "Unable to create workspace edit for code action " + label);
82+
return toResolve;
83+
}
84+
85+
protected static PsiElement getBinding(PsiElement node) {
86+
PsiElement parent = PsiTreeUtil.getParentOfType(node, PsiModifierListOwner.class);
87+
if (parent != null) {
88+
return parent;
89+
}
90+
return PsiTreeUtil.getParentOfType(node, PsiClass.class);
91+
}
92+
93+
protected String getLabel() {
94+
return Messages.getMessage("PrefixSlashToValueAttribute"); // uses Java syntax
95+
}
96+
}

src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/websocket/WebSocketConstants.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/** Copyright (c) 2022, 2024 IBM Corporation and others.
1+
/** Copyright (c) 2022, 2025 IBM Corporation and others.
22
*
33
* This program and the accompanying materials are made available under the
44
* terms of the Eclipse Public License v. 2.0 which is available at
@@ -46,6 +46,7 @@ public class WebSocketConstants {
4646
public static final String DIAGNOSTIC_CODE_ON_MESSAGE_DUPLICATE_METHOD = "OnMessageDuplicateMethod";
4747

4848
public static final String DIAGNOSTIC_SERVER_ENDPOINT = "ChangeInvalidServerEndpoint";
49+
public static final String DIAGNOSTIC_SERVER_ENDPOINT_WITHOUT_SLASH = "InvalidEndpointWithoutStartingSlash";
4950

5051
/*
5152
* https://jakarta.ee/specifications/websocket/2.0/websocket-spec-2.0.html#

src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/websocket/WebSocketDiagnosticsCollector.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2022, 2024 IBM Corporation and others.
2+
* Copyright (c) 2022, 2025 IBM Corporation and others.
33
*
44
* This program and the accompanying materials are made available under the
55
* terms of the Eclipse Public License v. 2.0 which is available at
@@ -253,7 +253,7 @@ private void serverEndpointErrorCheck(PsiClass type, List<Diagnostic> diagnostic
253253
if (!JDTUtils.hasLeadingSlash(path)) {
254254
diagnostics.add(createDiagnostic(annotation, unit,
255255
Messages.getMessage("ServerEndpointNoSlash"),
256-
WebSocketConstants.DIAGNOSTIC_SERVER_ENDPOINT, null, DiagnosticSeverity.Error));
256+
WebSocketConstants.DIAGNOSTIC_SERVER_ENDPOINT_WITHOUT_SLASH, null, DiagnosticSeverity.Error));
257257
}
258258
if (hasRelativePathURIs(path)) {
259259
diagnostics.add(createDiagnostic(annotation, unit,

src/main/resources/META-INF/plugin.xml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,10 @@
359359
group="jakarta"
360360
targetDiagnostic="jakarta-websocket#AddPathParamsAnnotation"
361361
implementationClass="io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.websocket.AddPathParamQuickFix"/>
362-
362+
<javaCodeActionParticipant kind="quickfix"
363+
group="jakarta"
364+
targetDiagnostic="jakarta-websocket#InvalidEndpointWithoutStartingSlash"
365+
implementationClass="io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.codeAction.proposal.quickfix.PrefixSlashAnnotationQuickFix"/>
363366
<!-- Servlet -->
364367
<javaCodeActionParticipant kind="quickfix"
365368
group="jakarta"

src/main/resources/io/openliberty/tools/intellij/lsp4jakarta/messages/messages.properties

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,3 +180,6 @@ ServerEndpointNoSlash = Server endpoint paths must start with a leading '/'.
180180
ServerEndpointRelative = Server endpoint paths must not contain the sequences '/../', '/./' or '//'.
181181
ServerEndpointNotLevel1 = Server endpoint paths must be a URI-template (level-1) or a partial URI.
182182
ServerEndpointDuplicateVar = Server endpoint paths must not use the same variable more than once in a path.
183+
184+
# WebSocketDiagnosticsQuickFix
185+
PrefixSlashToValueAttribute = Prefix value with '/'

src/test/java/io/openliberty/tools/intellij/lsp4jakarta/it/websocket/JakartaWebSocketTest.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2022, 2024 IBM Corporation and others.
2+
* Copyright (c) 2022, 2025 IBM Corporation and others.
33
*
44
* This program and the accompanying materials are made available under the
55
* terms of the Eclipse Public License v. 2.0 which is available at
@@ -163,11 +163,21 @@ public void testServerEndpointNoSlashURI() throws Exception {
163163
JakartaJavaDiagnosticsParams diagnosticsParams = new JakartaJavaDiagnosticsParams();
164164
diagnosticsParams.setUris(Arrays.asList(uri));
165165
Diagnostic d1 = JakartaForJavaAssert.d(7, 0, 23, "Server endpoint paths must start with a leading '/'.", DiagnosticSeverity.Error,
166-
"jakarta-websocket", "ChangeInvalidServerEndpoint");
166+
"jakarta-websocket", "InvalidEndpointWithoutStartingSlash");
167167
Diagnostic d2 = JakartaForJavaAssert.d(7, 0, 23, "Server endpoint paths must be a URI-template (level-1) or a partial URI.",
168168
DiagnosticSeverity.Error, "jakarta-websocket", "ChangeInvalidServerEndpoint");
169169

170170
JakartaForJavaAssert.assertJavaDiagnostics(diagnosticsParams, utils, d1, d2);
171+
172+
// Expected code actions
173+
JakartaJavaCodeActionParams codeActionsParams = createCodeActionParams(uri, d1);
174+
String newText = "package io.openliberty.sample.jakarta.websocket;\n\nimport jakarta.websocket.server.ServerEndpoint;\n\n" +
175+
"// Diagnostics:\n// + Server endpoint paths must start with a leading '/'.\n" +
176+
"// + Server endpoint paths must be a URI-template (level-1) or a partial URI.\n" +
177+
"@ServerEndpoint(\"/path\")\npublic class ServerEndpointNoSlash {\n}\n";
178+
TextEdit te = te(0, 0, 9, 0, newText);
179+
CodeAction ca = ca(uri, "Prefix value with '/'", d1, te);
180+
JakartaForJavaAssert.assertJavaCodeAction(codeActionsParams, utils, ca);
171181
}
172182

173183
@Test

0 commit comments

Comments
 (0)