Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*******************************************************************************
* Copyright (c) 2025 IBM Corporation and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.codeAction.proposal;

import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import io.openliberty.tools.intellij.lsp4mp4ij.psi.core.java.corrections.proposal.ASTRewriteCorrectionProposal;
import org.eclipse.lsp4j.CodeActionKind;

public class PrefixSlashAnnotationProposal extends ASTRewriteCorrectionProposal {
private final PsiAnnotation fAnnotation;

public PrefixSlashAnnotationProposal(String name, int relevance, PsiFile sourceCU, PsiElement declaringNode, PsiAnnotation annotationNode) {
super(name, CodeActionKind.QuickFix, declaringNode, relevance, sourceCU);
this.fAnnotation = annotationNode;
}

@Override
public void performUpdate() {
final String FORWARD_SLASH = "/";
final String ESCAPE_QUOTE = "\"";
if (fAnnotation != null) {
PsiElementFactory factory = JavaPsiFacade.getElementFactory(fAnnotation.getProject());
for (PsiNameValuePair pair : fAnnotation.getParameterList().getAttributes()) {
if (pair.getValue() instanceof PsiLiteralExpression literal && literal.getValue() instanceof String valueText) {
if (!valueText.startsWith(FORWARD_SLASH)) {
fAnnotation.setDeclaredAttributeValue(PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME, factory.createExpressionFromText(
String.format("%s%s%s%s", ESCAPE_QUOTE, FORWARD_SLASH, valueText, ESCAPE_QUOTE),
fAnnotation
));
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*******************************************************************************
* Copyright (c) 2025 IBM Corporation and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.codeAction.proposal.quickfix;

import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.JDTUtils;
import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.Messages;
import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.codeAction.proposal.PrefixSlashAnnotationProposal;
import io.openliberty.tools.intellij.lsp4mp4ij.psi.core.java.codeaction.IJavaCodeActionParticipant;
import io.openliberty.tools.intellij.lsp4mp4ij.psi.core.java.codeaction.JavaCodeActionContext;
import io.openliberty.tools.intellij.lsp4mp4ij.psi.core.java.codeaction.JavaCodeActionResolveContext;
import io.openliberty.tools.intellij.lsp4mp4ij.psi.core.java.corrections.proposal.ChangeCorrectionProposal;
import io.openliberty.tools.intellij.util.ExceptionUtil;
import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4mp.commons.codeaction.CodeActionResolveData;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;

public class PrefixSlashAnnotationQuickFix implements IJavaCodeActionParticipant {

private static final Logger LOGGER = Logger.getLogger(PrefixSlashAnnotationQuickFix.class.getName());

/**
* Returns the unique identifier of this code action participant.
*
* @return the unique identifier of this code action participant
*/
@Override
public String getParticipantId() {
return PrefixSlashAnnotationQuickFix.class.getName();
}

/**
* Return the code action list for a given compilation unit and null otherwise.
*
* @param context the java code action context.
* @param diagnostic the diagnostic which must be fixed and null otherwise.
* @return the code action list for a given compilation unit and null otherwise.
*/
@Override
public List<? extends CodeAction> getCodeActions(JavaCodeActionContext context, Diagnostic diagnostic) {
PsiElement node = context.getCoveredNode();
PsiElement parentType = getBinding(node);
if (parentType != null) {
List<CodeAction> codeActions = new ArrayList<>();
codeActions.add(JDTUtils.createCodeAction(context, diagnostic, getLabel(), getParticipantId()));
return codeActions;
}
return Collections.emptyList();
}

/**
* Returns the code action with the TextEdits filled in.
*
* @param context the code action context to resolve
* @return the code action with the TextEdits filled in
*/
@Override
public CodeAction resolveCodeAction(JavaCodeActionResolveContext context) {
final CodeAction toResolve = context.getUnresolved();
final PsiElement node = context.getCoveredNode();
PsiElement declaringNode = getBinding(context.getCoveredNode());
PsiAnnotation annotationNode = PsiTreeUtil.getParentOfType(node, PsiAnnotation.class);
String label = getLabel();
ChangeCorrectionProposal proposal = new PrefixSlashAnnotationProposal(label, 0, context.getSource().getCompilationUnit(), declaringNode, annotationNode);
ExceptionUtil.executeWithWorkspaceEditHandling(context, proposal, toResolve, LOGGER, "Unable to create workspace edit for code action " + label);
return toResolve;
}

protected static PsiElement getBinding(PsiElement node) {
PsiElement parent = PsiTreeUtil.getParentOfType(node, PsiModifierListOwner.class);
if (parent != null) {
return parent;
}
return PsiTreeUtil.getParentOfType(node, PsiClass.class);
}

protected String getLabel() {
return Messages.getMessage("PrefixSlashToValueAttribute"); // uses Java syntax
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public class WebSocketConstants {
public static final String DIAGNOSTIC_CODE_ON_MESSAGE_DUPLICATE_METHOD = "OnMessageDuplicateMethod";

public static final String DIAGNOSTIC_SERVER_ENDPOINT = "ChangeInvalidServerEndpoint";
public static final String DIAGNOSTIC_SERVER_ENDPOINT_WITHOUT_SLASH = "InvalidEndpointWithoutStartingSlash";

/*
* https://jakarta.ee/specifications/websocket/2.0/websocket-spec-2.0.html#
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ private void serverEndpointErrorCheck(PsiClass type, List<Diagnostic> diagnostic
if (!JDTUtils.hasLeadingSlash(path)) {
diagnostics.add(createDiagnostic(annotation, unit,
Messages.getMessage("ServerEndpointNoSlash"),
WebSocketConstants.DIAGNOSTIC_SERVER_ENDPOINT, null, DiagnosticSeverity.Error));
WebSocketConstants.DIAGNOSTIC_SERVER_ENDPOINT_WITHOUT_SLASH, null, DiagnosticSeverity.Error));
}
if (hasRelativePathURIs(path)) {
diagnostics.add(createDiagnostic(annotation, unit,
Expand Down
5 changes: 4 additions & 1 deletion src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,10 @@
group="jakarta"
targetDiagnostic="jakarta-websocket#AddPathParamsAnnotation"
implementationClass="io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.websocket.AddPathParamQuickFix"/>

<javaCodeActionParticipant kind="quickfix"
group="jakarta"
targetDiagnostic="jakarta-websocket#InvalidEndpointWithoutStartingSlash"
implementationClass="io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.codeAction.proposal.quickfix.PrefixSlashAnnotationQuickFix"/>
<!-- Servlet -->
<javaCodeActionParticipant kind="quickfix"
group="jakarta"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,6 @@ ServerEndpointNoSlash = Server endpoint paths must start with a leading '/'.
ServerEndpointRelative = Server endpoint paths must not contain the sequences '/../', '/./' or '//'.
ServerEndpointNotLevel1 = Server endpoint paths must be a URI-template (level-1) or a partial URI.
ServerEndpointDuplicateVar = Server endpoint paths must not use the same variable more than once in a path.

# WebSocketDiagnosticsQuickFix
PrefixSlashToValueAttribute = Prefix value with '/'
Original file line number Diff line number Diff line change
Expand Up @@ -163,11 +163,21 @@ public void testServerEndpointNoSlashURI() throws Exception {
JakartaJavaDiagnosticsParams diagnosticsParams = new JakartaJavaDiagnosticsParams();
diagnosticsParams.setUris(Arrays.asList(uri));
Diagnostic d1 = JakartaForJavaAssert.d(7, 0, 23, "Server endpoint paths must start with a leading '/'.", DiagnosticSeverity.Error,
"jakarta-websocket", "ChangeInvalidServerEndpoint");
"jakarta-websocket", "InvalidEndpointWithoutStartingSlash");
Diagnostic d2 = JakartaForJavaAssert.d(7, 0, 23, "Server endpoint paths must be a URI-template (level-1) or a partial URI.",
DiagnosticSeverity.Error, "jakarta-websocket", "ChangeInvalidServerEndpoint");

JakartaForJavaAssert.assertJavaDiagnostics(diagnosticsParams, utils, d1, d2);

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

@Test
Expand Down