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 10, 2024
1 parent ecd3b61 commit 5f8662f
Show file tree
Hide file tree
Showing 10 changed files with 346 additions and 92 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -196,11 +196,6 @@ public static ICompilationUnit resolveCompilationUnit(URI uri) {

IFile resource = (IFile) findResource(uri, ResourcesPlugin.getWorkspace().getRoot()::findFilesForLocationURI);
if(resource != null) {
for (ICompilationUnit workingCopy : JavaCore.getWorkingCopies(null)) {
if (uri.equals(toURI(toURI(workingCopy)))) {
return workingCopy;
}
}
return resolveCompilationUnit(resource);
} else {
return getFakeCompilationUnit(uri, new NullProgressMonitor());
Expand All @@ -212,6 +207,11 @@ public static ICompilationUnit resolveCompilationUnit(IFile resource) {
if(!ProjectUtils.isJavaProject(resource.getProject())){
return null;
}
for (ICompilationUnit workingCopy : JavaCore.getWorkingCopies(null)) {
if (resource.equals(workingCopy.getResource())) {
return workingCopy;
}
}
if (resource.getFileExtension() != null) {
String name = resource.getName();
if (org.eclipse.jdt.internal.core.util.Util.isJavaLikeFileName(name)) {
Expand Down
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 @@ -56,6 +56,7 @@
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.WorkingCopyOwner;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.manipulation.CoreASTProvider;
import org.eclipse.jdt.internal.core.OpenableElementInfo;
Expand Down Expand Up @@ -258,6 +259,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 Expand Up @@ -329,7 +334,13 @@ public IProblemRequestor getProblemRequestor(ICompilationUnit workingCopy) {
};
int flags = ICompilationUnit.FORCE_PROBLEM_DETECTION | ICompilationUnit.ENABLE_BINDINGS_RECOVERY | ICompilationUnit.ENABLE_STATEMENTS_RECOVERY;
synchronized(reconcileLock) {
unit.reconcile(ICompilationUnit.NO_AST, flags, wcOwner, monitor);
if (!org.eclipse.jdt.internal.core.CompilationUnit.DOM_BASED_OPERATIONS) {
unit.reconcile(ICompilationUnit.NO_AST, flags, wcOwner, monitor);
} else {
// ReconcileWorkingCopyOperation.executeOperation() creates AST twice with NO_AST
// see ReconcileWorkingCopyOperation.java, line 221
unit.reconcile(AST.getJLSLatest(), flags, wcOwner, monitor);
}
}
}

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 5f8662f

Please sign in to comment.