Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

changes for codeaction #331

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2020, 2024 IBM Corporation and others.
* Copyright (c) 2020, 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
Expand All @@ -16,6 +16,8 @@
import java.util.concurrent.CompletableFuture;
import java.util.logging.Logger;

import org.eclipse.lsp4j.CodeActionKind;
import org.eclipse.lsp4j.CodeActionOptions;
import org.eclipse.lsp4j.CompletionOptions;
import org.eclipse.lsp4j.InitializeParams;
import org.eclipse.lsp4j.InitializeResult;
Expand Down Expand Up @@ -69,6 +71,7 @@ private ServerCapabilities createServerCapabilities() {
capabilities.setTextDocumentSync(TextDocumentSyncKind.Full);
capabilities.setHoverProvider(Boolean.TRUE);
capabilities.setCompletionProvider(new CompletionOptions(Boolean.TRUE, Arrays.asList("=")));
capabilities.setCodeActionProvider(new CodeActionOptions(Arrays.asList(CodeActionKind.QuickFix)));
return capabilities;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2020, 2023 IBM Corporation and others.
* Copyright (c) 2020, 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
Expand All @@ -12,6 +12,10 @@
*******************************************************************************/
package io.openliberty.tools.langserver;

import io.openliberty.tools.langserver.codeactions.CodeActionProcessor;
import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.CodeActionParams;
import org.eclipse.lsp4j.Command;
import org.eclipse.lsp4j.DidChangeTextDocumentParams;
import org.eclipse.lsp4j.DidCloseTextDocumentParams;
import org.eclipse.lsp4j.DidOpenTextDocumentParams;
Expand Down Expand Up @@ -139,6 +143,12 @@ public CompletableFuture<CompletionItem> resolveCompletionItem(CompletionItem un
return CompletableFuture.completedFuture(unresolved);
}

@Override
public CompletableFuture<List<Either<Command, CodeAction>>> codeAction(CodeActionParams params) {
LOGGER.info("codeAction: "+ params.getTextDocument());
return new CodeActionProcessor(this,libertyLanguageServer).getCodeActions(params);
}

private void validate(List<String> uris) {
if (uris.isEmpty()) {
return;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*******************************************************************************
* 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.langserver.codeactions;

import io.openliberty.tools.langserver.LibertyLanguageServer;
import io.openliberty.tools.langserver.LibertyTextDocumentService;
import io.openliberty.tools.langserver.ls.LibertyTextDocument;
import io.openliberty.tools.langserver.utils.ParserFileHelperUtil;
import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.CodeActionKind;
import org.eclipse.lsp4j.CodeActionParams;
import org.eclipse.lsp4j.Command;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.TextDocumentItem;
import org.eclipse.lsp4j.TextEdit;
import org.eclipse.lsp4j.WorkspaceEdit;
import org.eclipse.lsp4j.jsonrpc.messages.Either;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public abstract class AbstractQuickfix {

protected LibertyTextDocumentService libertyTextDocumentService;
protected LibertyLanguageServer libertyLanguageServer;

protected AbstractQuickfix(LibertyTextDocumentService libertyTextDocumentService, LibertyLanguageServer libertyLanguageServer) {
this.libertyTextDocumentService = libertyTextDocumentService;
this.libertyLanguageServer = libertyLanguageServer;
}

public List<Either<Command, CodeAction>> apply(CodeActionParams params) {
TextDocumentItem openedDocument = libertyTextDocumentService.getOpenedDocument(params.getTextDocument().getUri());
List<Diagnostic> diagnostics = params.getContext().getDiagnostics();
List<Either<Command, CodeAction>> res = new ArrayList<>();
for (Diagnostic diagnostic : diagnostics) {
if (diagnostic.getCode() != null && getDiagnosticCode().equals(diagnostic.getCode().getLeft())) {
String line = new ParserFileHelperUtil().getLine(new LibertyTextDocument(openedDocument), diagnostic.getRange().getStart().getLine());
if (line != null) {
List<String> possibleProperties = retrievePossibleValues(openedDocument, diagnostic.getRange().getStart());
for (String mostProbableProperty : possibleProperties) {
res.add(Either.forRight(createCodeAction(params, diagnostic, mostProbableProperty)));
}
}
}
}
return res;
}

protected CodeAction createCodeAction(CodeActionParams params, Diagnostic diagnostic, String possibleProperty) {
CodeAction codeAction = new CodeAction("Replace value with " + possibleProperty);
codeAction.setDiagnostics(Collections.singletonList(diagnostic));
codeAction.setKind(CodeActionKind.QuickFix);
Map<String, List<TextEdit>> changes = new HashMap<>();
TextEdit textEdit = new TextEdit(diagnostic.getRange(), possibleProperty);
changes.put(params.getTextDocument().getUri(), List.of(textEdit));
codeAction.setEdit(new WorkspaceEdit(changes));
return codeAction;
}

protected abstract List<String> retrievePossibleValues(TextDocumentItem textDocumentItem, Position position);

protected abstract String getDiagnosticCode();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*******************************************************************************
* 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.langserver.codeactions;

import io.openliberty.tools.langserver.LibertyLanguageServer;
import io.openliberty.tools.langserver.LibertyTextDocumentService;
import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.CodeActionContext;
import org.eclipse.lsp4j.CodeActionKind;
import org.eclipse.lsp4j.CodeActionParams;
import org.eclipse.lsp4j.Command;
import org.eclipse.lsp4j.jsonrpc.messages.Either;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;

public class CodeActionProcessor {

private LibertyTextDocumentService libertyTextDocumentService;
private LibertyLanguageServer libertyLanguageServer;

public CodeActionProcessor(LibertyTextDocumentService libertyTextDocumentService, LibertyLanguageServer libertyLanguageServer) {
this.libertyTextDocumentService = libertyTextDocumentService;
this.libertyLanguageServer = libertyLanguageServer;
}

public CompletableFuture<List<Either<Command, CodeAction>>> getCodeActions(CodeActionParams params) {
CodeActionContext context = params.getContext();
if (context != null) {
List<Either<Command, CodeAction>> codeActions = new ArrayList<>();
List<String> codeActionsType = context.getOnly();
if (codeActionsType == null) {
codeActions.addAll(computeQuickfixes(params));
} else {
if (codeActionsType.contains(CodeActionKind.QuickFix)) {
codeActions.addAll(computeQuickfixes(params));
}
}
return CompletableFuture.supplyAsync(() -> codeActions);
} else {
return CompletableFuture.completedFuture(Collections.emptyList());
}
}

private List<Either<Command, CodeAction>> computeQuickfixes(CodeActionParams params) {
List<Either<Command, CodeAction>> allQuickfixes = new ArrayList<>();
allQuickfixes.addAll(new UnknownPropertyValueQuickfix(libertyTextDocumentService, libertyLanguageServer).apply(params));
return allQuickfixes;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*******************************************************************************
* 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.langserver.codeactions;

import io.openliberty.tools.langserver.LibertyLanguageServer;
import io.openliberty.tools.langserver.LibertyTextDocumentService;
import io.openliberty.tools.langserver.ls.LibertyTextDocument;
import io.openliberty.tools.langserver.model.propertiesfile.PropertiesEntryInstance;
import io.openliberty.tools.langserver.utils.ParserFileHelperUtil;
import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.TextDocumentItem;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.logging.Logger;
import java.util.stream.Collectors;

import static io.openliberty.tools.langserver.diagnostic.LibertyPropertiesDiagnosticService.ERROR_CODE_UNKNOWN_PROPERTY_VALUE;

public class UnknownPropertyValueQuickfix extends AbstractQuickfix {

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

public UnknownPropertyValueQuickfix(LibertyTextDocumentService libertyTextDocumentService, LibertyLanguageServer libertyLanguageServer) {
super(libertyTextDocumentService, libertyLanguageServer);
}

protected String getDiagnosticCode() {
return ERROR_CODE_UNKNOWN_PROPERTY_VALUE;
}

@Override
protected List<String> retrievePossibleValues(TextDocumentItem textDocumentItem,
Position position) {
try {
LibertyTextDocument openedDocument = libertyLanguageServer.getTextDocumentService().getOpenedDocument(textDocumentItem.getUri());
String line = new ParserFileHelperUtil().getLine(openedDocument, position);
PropertiesEntryInstance propertiesEntryInstance = new PropertiesEntryInstance(line, openedDocument);
CompletableFuture<List<CompletionItem>> completions = propertiesEntryInstance.getPropertyValueInstance().getCompletions("", position);
return completions.thenApply(completionItems -> completionItems.stream().map(it -> it.getTextEdit().getLeft().getNewText())
.collect(Collectors.toList()))
.get();
} catch (InterruptedException e) {
LOGGER.severe("Interruption while computing possible properties for quickfix. Error message is " + e.getMessage());
Thread.currentThread().interrupt();
return Collections.emptyList();
} catch (ExecutionException e) {
LOGGER.severe("Exception while computing possible properties for quickfix. Error message is " + e.getMessage());
return Collections.emptyList();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2022, 2024 IBM Corporation and others.
* Copyright (c) 2022, 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
Expand Down Expand Up @@ -39,6 +39,7 @@ public class LibertyPropertiesDiagnosticService {

private static final Logger LOGGER = Logger.getLogger(LibertyPropertiesDiagnosticService.class.getName());
private static final ResourceBundle DiagnosticMessages = ResourceBundle.getBundle("DiagnosticMessages", Locale.getDefault());
public static final String ERROR_CODE_UNKNOWN_PROPERTY_VALUE = "unknown_property_value";


public Map<String, PropertiesValidationResult> compute(String text, LibertyTextDocument openedDocument) {
Expand Down Expand Up @@ -86,7 +87,9 @@ private List<Diagnostic> computeInvalidValuesDiagnostic(PropertiesValidationResu

// Currently the last arg (getIntegerRange) is only used for the Integer messages which use {2}. Otherwise null is passed and is ignored by the other messages.
String message = MessageFormat.format(messageTemplate, validationResult.getValue(), property, ServerPropertyValues.getIntegerRange(property));
lspDiagnostics.add(new Diagnostic(computeRange(validationResult, lineContentInError), message, DiagnosticSeverity.Error, "Liberty Config Language Server"));
Diagnostic diagnostic = new Diagnostic(computeRange(validationResult, lineContentInError), message, DiagnosticSeverity.Error, "Liberty Config Language Server");
diagnostic.setCode(ERROR_CODE_UNKNOWN_PROPERTY_VALUE);
lspDiagnostics.add(diagnostic);
}
return lspDiagnostics;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2022, 2024 IBM Corporation and others.
* Copyright (c) 2022, 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
Expand All @@ -20,7 +20,6 @@

import io.openliberty.tools.langserver.LibertyConfigFileManager;
import io.openliberty.tools.langserver.ls.LibertyTextDocument;
import io.openliberty.tools.langserver.utils.ParserFileHelperUtil;

public class PropertiesEntryInstance {
private PropertiesKeyInstance propertyKeyInstance;
Expand Down Expand Up @@ -108,4 +107,9 @@ public CompletableFuture<List<CompletionItem>> getCompletions(Position position)
}
return CompletableFuture.completedFuture(Collections.emptyList());
}

public PropertiesValueInstance getPropertyValueInstance() {
return propertyValueInstance;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package io.openliberty.tools.langserver.codeactions;

import io.openliberty.tools.langserver.AbstractLibertyLanguageServerTest;
import org.awaitility.core.ConditionFactory;
import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.CodeActionContext;
import org.eclipse.lsp4j.CodeActionKind;
import org.eclipse.lsp4j.CodeActionParams;
import org.eclipse.lsp4j.Command;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.DidSaveTextDocumentParams;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.jsonrpc.messages.Either;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

import static org.awaitility.Awaitility.await;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

public abstract class AbstractQuickFixTest extends AbstractLibertyLanguageServerTest {

private static final Duration AWAIT_TIMEOUT = Duration.ofSeconds(10);
private static final Duration AWAIT_POLL_INTERVAL = Duration.ofMillis(5);


/**
* @param fileName inside src/test/resources/workspace/diagnostic/ folder
* @return
* @throws FileNotFoundException
*/
protected TextDocumentIdentifier initAnLaunchDiagnostic(String fileName) throws FileNotFoundException {
File f = new File("src/test/resources/workspace/codeaction/src/main/liberty/config/" + fileName);
FileInputStream streamWithContentToTest = new FileInputStream(f);
return initAndLaunchDiagnostic(f, streamWithContentToTest);
}

protected TextDocumentIdentifier initAndLaunchDiagnostic(File f, InputStream streamWithContentToTest) {
libertyLanguageServer = initializeLanguageServerWithFileUriString(streamWithContentToTest, f.toURI().toString());

TextDocumentIdentifier textDocumentIdentifier = new TextDocumentIdentifier(f.toURI().toString());
DidSaveTextDocumentParams params = new DidSaveTextDocumentParams(textDocumentIdentifier);
libertyLanguageServer.getTextDocumentService().didSave(params);

createAwait().untilAsserted(() -> assertNotNull(lastPublishedDiagnostics));
await().timeout(AWAIT_TIMEOUT).untilAsserted(() -> assertEquals(1, lastPublishedDiagnostics.getDiagnostics().size()));
return textDocumentIdentifier;
}

protected CompletableFuture<List<Either<Command, CodeAction>>> retrieveCodeActions(TextDocumentIdentifier textDocumentIdentifier, Diagnostic diagnostic) {
CodeActionContext context = new CodeActionContext(lastPublishedDiagnostics.getDiagnostics(), Collections.singletonList(CodeActionKind.QuickFix));
return libertyLanguageServer.getTextDocumentService().codeAction(new CodeActionParams(textDocumentIdentifier, diagnostic.getRange(), context));
}

protected ConditionFactory createAwait() {
return await().pollDelay(Duration.ZERO).pollInterval(AWAIT_POLL_INTERVAL).timeout(AWAIT_TIMEOUT);
}

protected void assertEqualCodeAction(CodeAction expected, CodeAction actual){
assertEquals(CodeActionKind.QuickFix, actual.getKind());
assertEquals(expected.getDiagnostics(),actual.getDiagnostics());
assertEquals(expected.getTitle(),actual.getTitle());
}

protected List<CodeAction> populateCodeActions(List<Diagnostic> diagnostics, String codeActionKind,String... codeActionTitles){
return Arrays.stream(codeActionTitles).sequential().map(codeActionTitle->{
CodeAction codeAction=new CodeAction(codeActionTitle);
codeAction.setDiagnostics(diagnostics);
codeAction.setKind(codeActionKind);
return codeAction;
}).collect(Collectors.toList());
}
}
Loading
Loading