Skip to content

Commit

Permalink
Improve code actions
Browse files Browse the repository at this point in the history
Signed-off-by: Snjezana Peco <[email protected]>
  • Loading branch information
snjeza committed Nov 7, 2024
1 parent ecd3b61 commit cab0b1a
Show file tree
Hide file tree
Showing 9 changed files with 333 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.util.Map;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IField;
Expand Down Expand Up @@ -110,8 +111,10 @@
import org.eclipse.jdt.ui.text.java.IProblemLocation;
import org.eclipse.lsp4j.CodeActionKind;
import org.eclipse.lsp4j.CodeActionParams;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.CheckConditionsOperation;
import org.eclipse.ltk.core.refactoring.CreateChangeOperation;
import org.eclipse.ltk.core.refactoring.NullChange;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;

/**
Expand All @@ -127,38 +130,99 @@ public RefactorProcessor(PreferenceManager preferenceManager) {
}

public List<ProposalKindWrapper> getProposals(CodeActionParams params, IInvocationContext context, IProblemLocation[] locations) throws CoreException {
return getProposals(params, context, locations, new NullProgressMonitor());
}

public List<ProposalKindWrapper> getProposals(CodeActionParams params, IInvocationContext context, IProblemLocation[] locations, IProgressMonitor monitor) throws CoreException {
ASTNode coveringNode = context.getCoveringNode();
if (monitor != null && monitor.isCanceled()) {
return Collections.emptyList();
}
if (coveringNode != null) {
ArrayList<ProposalKindWrapper> proposals = new ArrayList<>();

InvertBooleanUtility.getInverseConditionProposals(params, context, coveringNode, proposals);
if (monitor != null && monitor.isCanceled()) {
return Collections.emptyList();
}
getInverseLocalVariableProposals(params, context, coveringNode, proposals);

if (monitor != null && monitor.isCanceled()) {
return Collections.emptyList();
}
getMoveRefactoringProposals(params, context, coveringNode, proposals);

if (monitor != null && monitor.isCanceled()) {
return Collections.emptyList();
}
boolean noErrorsAtLocation = noErrorsAtLocation(locations, coveringNode);
if (noErrorsAtLocation) {
boolean problemsAtLocation = locations.length != 0;
getExtractVariableProposal(params, context, problemsAtLocation, proposals);
if (monitor != null && monitor.isCanceled()) {
return Collections.emptyList();
}
getExtractMethodProposal(params, context, coveringNode, problemsAtLocation, proposals);
if (monitor != null && monitor.isCanceled()) {
return Collections.emptyList();
}
getExtractFieldProposal(params, context, problemsAtLocation, proposals);
getInlineProposal(context, coveringNode, proposals);

if (monitor != null && monitor.isCanceled()) {
return Collections.emptyList();
}
getConvertAnonymousToNestedProposals(params, context, coveringNode, proposals);
if (monitor != null && monitor.isCanceled()) {
return Collections.emptyList();
}
getConvertAnonymousClassCreationsToLambdaProposals(context, coveringNode, proposals);
if (monitor != null && monitor.isCanceled()) {
return Collections.emptyList();
}
getConvertLambdaToAnonymousClassCreationsProposals(context, coveringNode, proposals);
if (monitor != null && monitor.isCanceled()) {
return Collections.emptyList();
}

getConvertVarTypeToResolvedTypeProposal(context, coveringNode, proposals);
if (monitor != null && monitor.isCanceled()) {
return Collections.emptyList();
}
getConvertResolvedTypeToVarTypeProposal(context, coveringNode, proposals);
if (monitor != null && monitor.isCanceled()) {
return Collections.emptyList();
}

getAddStaticImportProposals(context, coveringNode, proposals);
if (monitor != null && monitor.isCanceled()) {
return Collections.emptyList();
}

getConvertForLoopProposal(context, coveringNode, proposals);
if (monitor != null && monitor.isCanceled()) {
return Collections.emptyList();
}
getAssignToVariableProposals(context, coveringNode, locations, proposals, params);
getIntroduceParameterProposals(params, context, coveringNode, locations, proposals);
if (monitor != null && monitor.isCanceled()) {
return Collections.emptyList();
}
getExtractInterfaceProposal(params, context, proposals);
getChangeSignatureProposal(params, context, proposals);
if (monitor != null && monitor.isCanceled()) {
return Collections.emptyList();
}
getSurroundWithTryCatchProposal(context, proposals);
if (monitor != null && monitor.isCanceled()) {
return Collections.emptyList();
}
getChangeSignatureProposal(params, context, proposals);
if (monitor != null && monitor.isCanceled()) {
return Collections.emptyList();
}
getIntroduceParameterProposals(params, context, coveringNode, locations, proposals);
if (monitor != null && monitor.isCanceled()) {
return Collections.emptyList();
}
getInlineProposal(context, coveringNode, proposals);
if (monitor != null && monitor.isCanceled()) {
return Collections.emptyList();
}
}
return proposals;
}
Expand Down Expand Up @@ -334,15 +398,27 @@ private boolean getInlineProposal(IInvocationContext context, ASTNode node, Coll
// Inline Constant (static final field)
if (RefactoringAvailabilityTesterCore.isInlineConstantAvailable((IField) varBinding.getJavaElement())) {
InlineConstantRefactoring refactoring = new InlineConstantRefactoring(context.getCompilationUnit(), context.getASTRoot(), context.getSelectionOffset(), context.getSelectionLength());
if (refactoring != null && refactoring.checkInitialConditions(new NullProgressMonitor()).isOK() && refactoring.getReferences(new NullProgressMonitor(), new RefactoringStatus()).length > 0) {
refactoring.setRemoveDeclaration(refactoring.isDeclarationSelected());
refactoring.setReplaceAllReferences(refactoring.isDeclarationSelected());
CheckConditionsOperation check = new CheckConditionsOperation(refactoring, CheckConditionsOperation.FINAL_CONDITIONS);
final CreateChangeOperation create = new CreateChangeOperation(check, RefactoringStatus.FATAL);
create.run(new NullProgressMonitor());
if (refactoring != null) {
String label = ActionMessages.InlineConstantRefactoringAction_label;
int relevance = IProposalRelevance.INLINE_LOCAL;
ChangeCorrectionProposalCore proposal = new ChangeCorrectionProposalCore(label, create.getChange(), relevance);
ChangeCorrectionProposalCore proposal = new ChangeCorrectionProposalCore(label, null /*create.getChange()*/, relevance) {
@Override
public Change getChange() throws CoreException {
if (refactoring.checkInitialConditions(new NullProgressMonitor()).isOK() && refactoring.getReferences(new NullProgressMonitor(), new RefactoringStatus()).length > 0) {
refactoring.setRemoveDeclaration(refactoring.isDeclarationSelected());
refactoring.setReplaceAllReferences(refactoring.isDeclarationSelected());
CheckConditionsOperation check = new CheckConditionsOperation(refactoring, CheckConditionsOperation.FINAL_CONDITIONS);
final CreateChangeOperation create = new CreateChangeOperation(check, RefactoringStatus.FATAL);
try {
create.run(new NullProgressMonitor());
return create.getChange();
} catch (CoreException e) {
JavaLanguageServerPlugin.log(e);
}
}
return new NullChange();
}
};
resultingCollections.add(CodeActionHandler.wrap(proposal, CodeActionKind.RefactorInline));
return true;
}
Expand All @@ -357,8 +433,10 @@ private boolean getInlineProposal(IInvocationContext context, ASTNode node, Coll
}

// Inline Local Variable
// https://github.com/eclipse-jdtls/eclipse.jdt.ls/issues/3321
// I haven't enhanced it because InlineVariableTest.testInlineLocalVariableWithNoReferences() fails; can be enhanced
if (binding.getJavaElement() instanceof ILocalVariable localVar && RefactoringAvailabilityTesterCore.isInlineTempAvailable(localVar)) {
InlineTempRefactoring refactoring= new InlineTempRefactoring((VariableDeclaration) decl);
InlineTempRefactoring refactoring = new InlineTempRefactoring((VariableDeclaration) decl);
boolean status;
try {
status = refactoring.checkAllConditions(new NullProgressMonitor()).isOK();
Expand All @@ -382,13 +460,25 @@ private boolean getInlineProposal(IInvocationContext context, ASTNode node, Coll
// Inline Method
if (RefactoringAvailabilityTesterCore.isInlineMethodAvailable((IMethod) binding.getJavaElement())) {
InlineMethodRefactoring refactoring = InlineMethodRefactoring.create(context.getCompilationUnit(), context.getASTRoot(), context.getSelectionOffset(), context.getSelectionLength());
if (refactoring != null && refactoring.checkInitialConditions(new NullProgressMonitor()).isOK()) {
CheckConditionsOperation check = new CheckConditionsOperation(refactoring, CheckConditionsOperation.FINAL_CONDITIONS);
final CreateChangeOperation create = new CreateChangeOperation(check, RefactoringStatus.FATAL);
create.run(new NullProgressMonitor());
if (refactoring != null) {
String label = ActionMessages.InlineMethodRefactoringAction_label;
int relevance = IProposalRelevance.INLINE_LOCAL;
ChangeCorrectionProposalCore proposal = new ChangeCorrectionProposalCore(label, create.getChange(), relevance);
ChangeCorrectionProposalCore proposal = new ChangeCorrectionProposalCore(label, null /*create.getChange()*/, relevance) {
@Override
public Change getChange() throws CoreException {
if (refactoring.checkInitialConditions(new NullProgressMonitor()).isOK()) {
CheckConditionsOperation check = new CheckConditionsOperation(refactoring, CheckConditionsOperation.FINAL_CONDITIONS);
final CreateChangeOperation create = new CreateChangeOperation(check, RefactoringStatus.FATAL);
try {
create.run(new NullProgressMonitor());
return create.getChange();
} catch (CoreException e) {
JavaLanguageServerPlugin.log(e);
}
}
return new NullChange();
}
};
resultingCollections.add(CodeActionHandler.wrap(proposal, CodeActionKind.RefactorInline));
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,10 @@ public IStatus validateDocument(String uri, boolean debounce, IProgressMonitor m
}

toValidate.add(unit);
if (!unit.equals(sharedASTProvider.getActiveJavaElement())) {
sharedASTProvider.disposeAST();
}
sharedASTProvider.setActiveJavaElement(unit);
if (debounce && publishDiagnosticsJob != null) {
publishDiagnosticsJob.cancel();
publishDiagnosticsJob.setRule(null);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*******************************************************************************
* Copyright (c) 2016-2024 Red Hat Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.ls.core.internal.handlers;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.manipulation.CoreASTProvider;
import org.eclipse.jdt.internal.corext.refactoring.ExceptionInfo;
import org.eclipse.jdt.internal.corext.refactoring.ParameterInfo;
import org.eclipse.jdt.internal.corext.refactoring.RefactoringAvailabilityTesterCore;
import org.eclipse.jdt.internal.corext.refactoring.structure.ChangeSignatureProcessor;
import org.eclipse.jdt.internal.corext.util.JdtFlags;
import org.eclipse.jdt.ls.core.internal.JDTUtils;
import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin;
import org.eclipse.jdt.ls.core.internal.handlers.ChangeSignatureHandler.MethodException;
import org.eclipse.jdt.ls.core.internal.handlers.ChangeSignatureHandler.MethodParameter;
import org.eclipse.jdt.ls.core.internal.text.correction.ChangeSignatureInfo;
import org.eclipse.jdt.ls.core.internal.text.correction.CodeActionUtility;
import org.eclipse.jdt.ui.text.java.IInvocationContext;
import org.eclipse.lsp4j.CodeActionParams;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;

public class ChangeSignatureInfoHandler {

/**
*
*/
private static final String CANNOT_CHANGE_SIGNATURE = "Cannot change signature.";

public static ChangeSignatureInfo getChangeSignatureInfo(CodeActionParams params, IProgressMonitor monitor) {
if (monitor.isCanceled()) {
return null;
}
final ICompilationUnit unit = JDTUtils.resolveCompilationUnit(params.getTextDocument().getUri());
if (unit == null || monitor.isCanceled()) {
return null;
}
CompilationUnit astRoot = CoreASTProvider.getInstance().getAST(unit, CoreASTProvider.WAIT_YES, monitor);
if (astRoot == null || monitor.isCanceled()) {
return null;
}
IInvocationContext context = CodeActionHandler.getContext(unit, astRoot, params.getRange());
ASTNode methodNode = CodeActionUtility.inferASTNode(context.getCoveringNode(), MethodDeclaration.class);
if (methodNode == null) {
return null;
}
IMethodBinding methodBinding = ((MethodDeclaration) methodNode).resolveBinding();
if (methodBinding == null) {
return null;
}
IJavaElement element = methodBinding.getJavaElement();
if (element instanceof IMethod method) {
try {
ChangeSignatureProcessor processor = new ChangeSignatureProcessor(method);
if (RefactoringAvailabilityTesterCore.isChangeSignatureAvailable(method)) {
RefactoringStatus status = processor.checkInitialConditions(new NullProgressMonitor());
if (status.isOK()) {
List<MethodParameter> parameters = new ArrayList<>();
for (ParameterInfo info : processor.getParameterInfos()) {
parameters.add(new MethodParameter(info.getOldTypeName(), info.getOldName(), info.getDefaultValue() == null ? "null" : info.getDefaultValue(), info.getOldIndex()));
}
List<MethodException> exceptions = new ArrayList<>();
for (ExceptionInfo info : processor.getExceptionInfos()) {
exceptions.add(new MethodException(info.getFullyQualifiedName(), info.getElement().getHandleIdentifier()));
}
return new ChangeSignatureInfo(method.getHandleIdentifier(), JdtFlags.getVisibilityString(processor.getVisibility()), processor.getReturnTypeString(), method.getElementName(),
parameters.toArray(MethodParameter[]::new), exceptions.toArray(MethodException[]::new));
} else {
return new ChangeSignatureInfo(CANNOT_CHANGE_SIGNATURE + status.getMessageMatchingSeverity(status.getSeverity()));
}
}
} catch (CoreException e) {
JavaLanguageServerPlugin.logException(e);
}
}
return new ChangeSignatureInfo(CANNOT_CHANGE_SIGNATURE);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ public List<Either<Command, CodeAction>> getCodeActionCommands(CodeActionParams

if (containsKind(codeActionKinds, CodeActionKind.Refactor)) {
try {
List<ProposalKindWrapper> refactorProposals = this.refactorProcessor.getProposals(params, context, locations);
List<ProposalKindWrapper> refactorProposals = this.refactorProcessor.getProposals(params, context, locations, monitor);
refactorProposals.sort(comparator);
proposals.addAll(refactorProposals);
} catch (CoreException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
import org.eclipse.jdt.ls.core.internal.managers.TelemetryManager;
import org.eclipse.jdt.ls.core.internal.preferences.PreferenceManager;
import org.eclipse.jdt.ls.core.internal.preferences.Preferences;
import org.eclipse.jdt.ls.core.internal.text.correction.ChangeSignatureInfo;
import org.eclipse.lsp4j.CallHierarchyIncomingCall;
import org.eclipse.lsp4j.CallHierarchyIncomingCallsParams;
import org.eclipse.lsp4j.CallHierarchyItem;
Expand Down Expand Up @@ -1134,6 +1135,12 @@ public CompletableFuture<RefactorWorkspaceEdit> getRefactorEdit(GetRefactorEditP
return computeAsync((monitor) -> GetRefactorEditHandler.getEditsForRefactor(params));
}

@Override
public CompletableFuture<ChangeSignatureInfo> getChangeSignatureInfo(CodeActionParams params) {
debugTrace(">> java/getChangeSignatureInfo");
return computeAsync((monitor) -> ChangeSignatureInfoHandler.getChangeSignatureInfo(params, monitor));
}

@Override
public CompletableFuture<List<SelectionInfo>> inferSelection(InferSelectionParams params) {
debugTrace(">> java/inferSelection");
Expand Down
Loading

0 comments on commit cab0b1a

Please sign in to comment.