Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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,182 @@
/*******************************************************************************
* Copyright (c) 2025 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
******************************************************************************/
package com.redhat.devtools.lsp4ij.features.codeAction;

import com.redhat.devtools.lsp4ij.fixtures.LSPCodeActionFixtureTestCase;

/**
* Test case for InvalidWebFilter quick fix
*/
public class CodeActionCancellationTest extends LSPCodeActionFixtureTestCase {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To show in action the cancel support easily, I suggest that you remove this classe and you do test in the same WebFilterQuickFixTest.

This class could define a private method like

assertCodeActionss(boolen simulateCancellation , @NotNull String... expectedActions) (which will call

assertCodeActions(TEST_FILE_NAME,
            IntentionActionKind.QUICK_FIX_ONLY,
            // language=JAVA
            """
            package io.openliberty.sample.jakarta.servlet;
...

and after that you could define 2 tests methods:

public void testWebFilterQuickFix() {
     assertCodeActions(false,  "Add the `servletNames` attribute to @WebFilter",
            "Add the `urlPatterns` attribute to @WebFilter",
            "Add the `value` attribute to @WebFilter");
}

public void testWebFilterQuickFixWithCancellation() {
   assertCodeActions(true);
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@angelozerr I’ve made the modification as suggested, with a small change — since expectedActions is included in the request, I had to add that argument for both tests. Thanks!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do my suggestion to fix tests


private static final String NOT_JAVA_EXTENSION = "javax";
private static final String TEST_FILE_NAME = "InvalidWebFilter." + NOT_JAVA_EXTENSION;

public CodeActionCancellationTest() {
super("*." + NOT_JAVA_EXTENSION);
}

public void testWebFilterQuickFix() {

assertCodeActions(TEST_FILE_NAME,
IntentionActionKind.QUICK_FIX_ONLY,
// language=JAVA
"""
package io.openliberty.sample.jakarta.servlet;
import jakarta.servlet.Filter;
import jakarta.servlet.annotation.WebFilter;
@WebFilter(<caret>)
public abstract class InvalidWebFilter implements Filter {
}""",
// language=JSON
"""
[
{
"title": "Add the `servletNames` attribute to @WebFilter",
"kind": "quickfix",
"diagnostics": [
{
"range": {
"start": {
"line": 5,
"character": 0
},
"end": {
"line": 5,
"character": 12
}
},
"severity": 1,
"code": "CompleteWebFilterAttributes",
"source": "jakarta-servlet",
"message": "The annotation @WebFilter must define the attribute \\u0027urlPatterns\\u0027, \\u0027servletNames\\u0027 or \\u0027value\\u0027."
}
],
"data": {
"participantId": "io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.servlet.CompleteFilterAnnotationQuickFix",
"documentUri": "unused",
"range": {
"start": {
"line": 5,
"character": 0
},
"end": {
"line": 5,
"character": 12
}
},
"extendedData": {
"annotation": "jakarta.servlet.annotation.WebFilter",
"attribute": "servletNames",
"diagnosticCode": "CompleteWebFilterAttributes"
},
"resourceOperationSupported": true,
"commandConfigurationUpdateSupported": false
}
},
{
"title": "Add the `urlPatterns` attribute to @WebFilter",
"kind": "quickfix",
"diagnostics": [
{
"range": {
"start": {
"line": 5,
"character": 0
},
"end": {
"line": 5,
"character": 12
}
},
"severity": 1,
"code": "CompleteWebFilterAttributes",
"source": "jakarta-servlet",
"message": "The annotation @WebFilter must define the attribute \\u0027urlPatterns\\u0027, \\u0027servletNames\\u0027 or \\u0027value\\u0027."
}
],
"data": {
"participantId": "io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.servlet.CompleteFilterAnnotationQuickFix",
"documentUri": "unused",
"range": {
"start": {
"line": 5,
"character": 0
},
"end": {
"line": 5,
"character": 12
}
},
"extendedData": {
"annotation": "jakarta.servlet.annotation.WebFilter",
"attribute": "urlPatterns",
"diagnosticCode": "CompleteWebFilterAttributes"
},
"resourceOperationSupported": true,
"commandConfigurationUpdateSupported": false
}
},
{
"title": "Add the `value` attribute to @WebFilter",
"kind": "quickfix",
"diagnostics": [
{
"range": {
"start": {
"line": 5,
"character": 0
},
"end": {
"line": 5,
"character": 12
}
},
"severity": 1,
"code": "CompleteWebFilterAttributes",
"source": "jakarta-servlet",
"message": "The annotation @WebFilter must define the attribute \\u0027urlPatterns\\u0027, \\u0027servletNames\\u0027 or \\u0027value\\u0027."
}
],
"data": {
"participantId": "io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.servlet.CompleteFilterAnnotationQuickFix",
"documentUri": "unused",
"range": {
"start": {
"line": 5,
"character": 0
},
"end": {
"line": 5,
"character": 12
}
},
"extendedData": {
"annotation": "jakarta.servlet.annotation.WebFilter",
"attribute": "value",
"diagnosticCode": "CompleteWebFilterAttributes"
},
"resourceOperationSupported": true,
"commandConfigurationUpdateSupported": false
}
}
]
""",
true, // simulateCancellation = true
"Add the `servletNames` attribute to @WebFilter",
"Add the `urlPatterns` attribute to @WebFilter",
"Add the `value` attribute to @WebFilter"
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ public abstract class InvalidWebFilter implements Filter {
}
]"""
,
false, // simulateCancellation = false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As simulateCancellation is a specific case, please remove this change and define

 List<IntentionAction> assertCodeActions(@NotNull String fileName,
                                                      @NotNull IntentionActionKind kind,
                                                      @NotNull String editorContentText,
                                                      @NotNull List<CodeAction> codeActions,
                                                      @NotNull String... expectedActions)

which call the method with simulateCancellation=false

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've addressed the suggestion. Thank you @angelozerr

"Add the `servletNames` attribute to @WebFilter",
"Add the `urlPatterns` attribute to @WebFilter",
"Add the `value` attribute to @WebFilter");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

/**
Expand Down Expand Up @@ -61,12 +62,13 @@ protected List<IntentionAction> assertCodeActions(@NotNull String fileName,
@NotNull IntentionActionKind kind,
@NotNull String editorContentText,
@NotNull String codeActionsJson,
boolean simulateCancellation,
@NotNull String... expectedActions) {
List<CodeAction> codeActions = JSONUtils.getLsp4jGson()
.fromJson(codeActionsJson, new TypeToken<List<CodeAction>>() {
}.getType());

return assertCodeActions(fileName, kind, editorContentText, codeActions, expectedActions);
return assertCodeActions(fileName, kind, editorContentText, codeActions, simulateCancellation, expectedActions);
}

/**
Expand All @@ -82,6 +84,7 @@ protected List<IntentionAction> assertCodeActions(@NotNull String fileName,
@NotNull IntentionActionKind kind,
@NotNull String editorContentText,
@NotNull List<CodeAction> codeActions,
boolean simulateCancellation,
@NotNull String... expectedActions) {
List<Either<Command, CodeAction>> wrappedCodeActions = codeActions.stream()
.distinct()
Expand All @@ -96,8 +99,20 @@ protected List<IntentionAction> assertCodeActions(@NotNull String fileName,
.collect(Collectors.toList());

MockLanguageServer.INSTANCE.setTimeToProceedQueries(200);
MockLanguageServer.INSTANCE.setCodeActions(wrappedCodeActions);
MockLanguageServer.INSTANCE.setPublishDiagnostics(diagnostics);

if (simulateCancellation) {
// Simulate a cancelled CodeAction request
MockLanguageServer.INSTANCE.setCodeActionHandler(params -> {
CompletableFuture<List<Either<Command, CodeAction>>> cancelled = new CompletableFuture<>();
cancelled.cancel(true);
return cancelled;
});
} else {
// Normal code action handling
MockLanguageServer.INSTANCE.setCodeActions(wrappedCodeActions);
}

myFixture.configureByText(fileName, editorContentText);

// Collect IntelliJ Quick fixes / Intention actions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,12 @@ public void setDocumentSymbols(DocumentSymbol... documentSymbols) {
this.textDocumentService.setDocumentSymbols(Arrays.asList(documentSymbols));
}

public void setCodeActionHandler(Function<CodeActionParams, CompletableFuture<List<Either<Command, CodeAction>>>> handler) {
if (this.textDocumentService != null) {
this.textDocumentService.setCodeActionHandler(handler);
}
}

@Override
public NotebookDocumentService getNotebookDocumentService() {
return new NotebookDocumentService() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ public class MockTextDocumentService implements TextDocumentService {
private SemanticTokens mockSemanticTokens;
private List<FoldingRange> foldingRanges;
public int codeActionRequests = 0;
private Function<CodeActionParams, CompletableFuture<List<Either<Command, CodeAction>>>> codeActionHandler;

public <U> MockTextDocumentService(Function<U, CompletableFuture<U>> futureFactory) {
this._futureFactory = futureFactory;
Expand Down Expand Up @@ -147,11 +148,15 @@ public CompletableFuture<List<DocumentLink>> documentLink(DocumentLinkParams par
@Override
public CompletableFuture<List<Either<Command, CodeAction>>> codeAction(CodeActionParams params) {
codeActionRequests++;
// For a custom handler
if (codeActionHandler != null) {
return codeActionHandler.apply(params);
}
// Filter code actions by using params.getContext().getOnly()
var only = (params.getContext() != null && params.getContext().getOnly() != null && !params.getContext().getOnly().isEmpty()) ?
params.getContext().getOnly() : null;
var filteredCodeActions = mockCodeActions;
if (only != null) {
if (only != null && mockCodeActions != null) {
filteredCodeActions = mockCodeActions
.stream()
.filter(ca -> {
Expand Down Expand Up @@ -411,6 +416,10 @@ public void setMockSelectionRanges(List<SelectionRange> mockSelectionRanges) {
this.mockSelectionRanges = mockSelectionRanges;
}

public void setCodeActionHandler(Function<CodeActionParams, CompletableFuture<List<Either<Command, CodeAction>>>> handler) {
this.codeActionHandler = handler;
}

@Override
public CompletableFuture<List<SelectionRange>> selectionRange(SelectionRangeParams params) {
// Find the mock selection ranges that apply to the specified position. This allows us to have a single mock
Expand Down
Loading