Skip to content

Commit

Permalink
code actions for quickfix for invalid property (#339)
Browse files Browse the repository at this point in the history
* code actions for quickfix for invalid property

Signed-off-by: Arun Venmany <[email protected]>

* changes based on review comments

Signed-off-by: Arun Venmany <[email protected]>

* changes based on review comments

Signed-off-by: Arun Venmany <[email protected]>

---------

Signed-off-by: Arun Venmany <[email protected]>
  • Loading branch information
arunvenmany-ibm authored Feb 3, 2025
1 parent a61f68b commit 01ad3da
Show file tree
Hide file tree
Showing 11 changed files with 426 additions and 6 deletions.
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 @@ -13,9 +13,12 @@
package io.openliberty.tools.langserver;

import java.util.Arrays;
import java.util.List;
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 +72,7 @@ private ServerCapabilities createServerCapabilities() {
capabilities.setTextDocumentSync(TextDocumentSyncKind.Full);
capabilities.setHoverProvider(Boolean.TRUE);
capabilities.setCompletionProvider(new CompletionOptions(Boolean.TRUE, Arrays.asList("=")));
capabilities.setCodeActionProvider(new CodeActionOptions(List.of(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.CodeActionParticipant;
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 CodeActionParticipant(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,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 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 CodeActionParticipant {

private LibertyTextDocumentService libertyTextDocumentService;
private LibertyLanguageServer libertyLanguageServer;

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

/**
*
* @param params
* @return
*/
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();
// code action type is coming null from vscode
if (codeActionsType == null || 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) {
// compute InvalidPropertyValueQuickfix
// need to change this logic when we have more types of quick fixes
return new ArrayList<>(new InvalidPropertyValueQuickfix(libertyTextDocumentService, libertyLanguageServer)
.apply(params));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*******************************************************************************
* 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 CodeActionQuickfixFactory {

protected LibertyTextDocumentService libertyTextDocumentService;
protected LibertyLanguageServer libertyLanguageServer;

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

/**
* returns list of code actions or commands.
* called from CodeActionParticipant
* @param params code action params
* @return codeaction
*/
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 && getErrorCode().equals(diagnostic.getCode().getLeft())) {
String line = new ParserFileHelperUtil().getLine(new LibertyTextDocument(openedDocument), diagnostic.getRange().getStart().getLine());
if (line != null) {
// fetch all completion values and shows them as quick fix
// completion returns empty list if no completion item is present
List<String> possibleProperties = retrieveCompletionValues(openedDocument, diagnostic.getRange().getStart());
for (String mostProbableProperty : possibleProperties) {
// expected format for a code action is <Command,CodeAction>
res.add(Either.forRight(createCodeAction(params, diagnostic, mostProbableProperty)));
}
}
}
}
return res;
}

/**
* used to create code action object for quickfix
* @param params codeaction params
* @param diagnostic diagnostic for which code action to be shown
* @param possibleProperty completion value
* @return codeaction
*/
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> retrieveCompletionValues(TextDocumentItem textDocumentItem, Position position);

protected abstract String getErrorCode();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*******************************************************************************
* 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.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Logger;
import java.util.stream.Collectors;

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

public class InvalidPropertyValueQuickfix extends CodeActionQuickfixFactory {

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

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

protected String getErrorCode() {
return ERROR_CODE_INVALID_PROPERTY_VALUE;
}

/**
* retrieve list of completion items for a property key
* @param textDocumentItem text document
* @param position current position, used to compute key
* @return list of string of completion item names
*/
@Override
protected List<String> retrieveCompletionValues(TextDocumentItem textDocumentItem,
Position position) {
List<String> completionValues = new ArrayList<>();
try {
LibertyTextDocument openedDocument = libertyLanguageServer.getTextDocumentService().getOpenedDocument(textDocumentItem.getUri());
String line = new ParserFileHelperUtil().getLine(openedDocument, position);
PropertiesEntryInstance propertiesEntryInstance = new PropertiesEntryInstance(line, openedDocument);
// get all completions for current property key
CompletableFuture<List<CompletionItem>> completions = propertiesEntryInstance.getPropertyValueInstance().getCompletions("", position);
// map text values from completion items
completionValues = 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();
} catch (Exception e) {
LOGGER.severe("Exception while computing possible properties for quickfix. Error message is " + e.getMessage());
}
return completionValues;
}
}
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,7 +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_INVALID_PROPERTY_VALUE = "unknown_property_value";

public Map<String, PropertiesValidationResult> compute(String text, LibertyTextDocument openedDocument) {
Map<String, PropertiesValidationResult> errors = new HashMap<>();
Expand Down Expand Up @@ -86,7 +86,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_INVALID_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 Down Expand Up @@ -108,4 +108,8 @@ public CompletableFuture<List<CompletionItem>> getCompletions(Position position)
}
return CompletableFuture.completedFuture(Collections.emptyList());
}

public PropertiesValueInstance getPropertyValueInstance() {
return propertyValueInstance;
}
}
Loading

0 comments on commit 01ad3da

Please sign in to comment.