[java-refactoring] Inline delegating variable: an ability to keep the current variable name

Fixes IDEA-371649 Inline variable using the later name

GitOrigin-RevId: a31d332762af3721245af31841562ff48a0cb3a9
This commit is contained in:
Tagir Valeev
2025-05-09 12:21:49 +02:00
committed by intellij-monorepo-bot
parent 439a3ccff8
commit 1cadb8a667
29 changed files with 236 additions and 70 deletions

View File

@@ -16,6 +16,7 @@ import com.intellij.openapi.util.NlsContexts;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.controlFlow.AnalysisCanceledException;
import com.intellij.psi.controlFlow.ControlFlowUtil;
import com.intellij.psi.controlFlow.DefUseUtil;
import com.intellij.psi.util.JavaPsiPatternUtil;
import com.intellij.psi.util.PsiTreeUtil;
@@ -41,13 +42,17 @@ import org.jetbrains.annotations.Nullable;
import java.util.*;
import static java.util.Objects.requireNonNull;
public final class InlineLocalHandler extends JavaInlineActionHandler {
private enum InlineMode {
public enum InlineMode {
CHECK_CONFLICTS,
ASK,
HIGHLIGHT_CONFLICTS,
INLINE_ONE,
INLINE_ALL_AND_DELETE
INLINE_ALL_AND_DELETE,
INLINE_ALL_KEEP_OLD_NAME, // the same as INLINE_ALL_AND_DELETE, just display different name
INLINE_ALL_RENAME_INITIALIZER
}
private static final Logger LOG = Logger.getInstance(InlineLocalHandler.class);
@@ -104,10 +109,10 @@ public final class InlineLocalHandler extends JavaInlineActionHandler {
return doInline(context, (PsiVariable)Objects.requireNonNull(context.element()), refExpr, mode);
}
private static ModCommand doInline(@NotNull ActionContext context,
@NotNull PsiVariable var,
@Nullable PsiReferenceExpression refExpr,
@NotNull InlineMode mode) {
public static @NotNull ModCommand doInline(@NotNull ActionContext context,
@NotNull PsiVariable var,
@Nullable PsiReferenceExpression refExpr,
@NotNull InlineMode mode) {
List<PsiReferenceExpression> allRefs =
refExpr != null && mode == InlineMode.INLINE_ONE ? List.of(refExpr) :
VariableAccessUtils.getVariableReferences(var);
@@ -160,6 +165,13 @@ public final class InlineLocalHandler extends JavaInlineActionHandler {
new InlineLocalStep(variable, refExpr, InlineMode.INLINE_ALL_AND_DELETE, allRefs));
}
private static @NotNull ModCommand createRenameChooser(@NotNull PsiVariable variable,
@NotNull List<? extends PsiElement> allRefs) {
return ModCommand.chooseAction(getRefactoringName(variable),
new InlineLocalStep(variable, null, InlineMode.INLINE_ALL_KEEP_OLD_NAME, allRefs),
new InlineLocalStep(variable, null, InlineMode.INLINE_ALL_RENAME_INITIALIZER, allRefs));
}
private static ModCommand createConflictChooser(PsiLocalVariable variable,
PsiReferenceExpression refExpr,
@@ -238,9 +250,18 @@ public final class InlineLocalHandler extends JavaInlineActionHandler {
}
}
if (mode == InlineMode.ASK && refExpr != null && refsToInlineList.size() > 1 && refsToInlineList.contains(refExpr) &&
EditorSettingsExternalizable.getInstance().isShowInlineLocalDialog()) {
return createChooser(local, refExpr, refsToInlineList);
if (mode == InlineMode.ASK) {
if (refExpr != null && refsToInlineList.size() > 1 && refsToInlineList.contains(refExpr) &&
EditorSettingsExternalizable.getInstance().isShowInlineLocalDialog()) {
return createChooser(local, refExpr, refsToInlineList);
}
if (defToInline == local.getInitializer() && PsiUtil.skipParenthesizedExprDown(defToInline) instanceof PsiReferenceExpression ref &&
ControlFlowUtil.isEffectivelyFinal(local, containerBlock)) {
PsiElement target = ref.resolve();
if (PsiUtil.isJvmLocalVariable(target)) {
return createRenameChooser(local, refsToInlineList);
}
}
}
final PsiElement[] refsToInline = PsiUtilCore.toPsiElementArray(refsToInlineList);
@@ -302,6 +323,11 @@ public final class InlineLocalHandler extends JavaInlineActionHandler {
}
Project project = context.project();
if (mode == InlineMode.INLINE_ALL_RENAME_INITIALIZER &&
PsiUtil.skipParenthesizedExprDown(defToInline) instanceof PsiReferenceExpression ref &&
ref.resolve() instanceof PsiVariable nextVar) {
return ModCommand.psiUpdate(context, updater -> renameNextVariable(local, nextVar, updater));
}
boolean inlineAll = mode != InlineMode.INLINE_ONE;
return ModCommand.psiUpdate(context, updater -> {
PsiExpression writableDef = updater.getWritable(defToInline);
@@ -339,6 +365,18 @@ public final class InlineLocalHandler extends JavaInlineActionHandler {
});
}
private static void renameNextVariable(@NotNull PsiLocalVariable local, @NotNull PsiVariable nextVar, @NotNull ModPsiUpdater updater) {
PsiVariable writableNextVar = updater.getWritable(nextVar);
List<PsiReferenceExpression> refs = VariableAccessUtils.getVariableReferences(writableNextVar);
new CommentTracker().deleteAndRestoreComments(updater.getWritable(local));
writableNextVar.setName(local.getName());
for (PsiReferenceExpression nextVarRef : refs) {
if (nextVarRef.isValid()) {
nextVarRef.handleElementRename(local.getName());
}
}
}
private record InnerClassUsages(List<PsiElement> innerClassesWithUsages, List<PsiElement> innerClassUsages) {
private static @NotNull InnerClassUsages getUsages(@NotNull PsiLocalVariable local, @NotNull List<PsiReferenceExpression> allRefs) {
final List<PsiElement> innerClassesWithUsages = new ArrayList<>();
@@ -510,16 +548,16 @@ public final class InlineLocalHandler extends JavaInlineActionHandler {
}
private static class InlineLocalStep implements ModCommandAction {
private final @NotNull PsiVariable myPattern;
private final @NotNull PsiVariable myVariable;
private final @Nullable PsiReferenceExpression myRefExpr;
private final @NotNull InlineMode myMode;
private final @NotNull Collection<? extends PsiElement> myAllRefs;
private InlineLocalStep(@NotNull PsiVariable pattern,
private InlineLocalStep(@NotNull PsiVariable variable,
@Nullable PsiReferenceExpression refExpr,
@NotNull InlineMode mode,
@NotNull Collection<? extends PsiElement> allRefs) {
myPattern = pattern;
myVariable = variable;
myRefExpr = refExpr;
myMode = mode;
myAllRefs = allRefs;
@@ -531,7 +569,7 @@ public final class InlineLocalHandler extends JavaInlineActionHandler {
return ModCommand.highlight(myAllRefs.toArray(PsiElement.EMPTY_ARRAY)).andThen(
myAllRefs.stream().findFirst().map(ModCommand::select).orElse(ModCommand.nop()));
}
return doInline(context, myPattern, myRefExpr, myMode);
return doInline(context, myVariable, myRefExpr, myMode);
}
@Override
@@ -549,6 +587,10 @@ public final class InlineLocalHandler extends JavaInlineActionHandler {
case ASK -> JavaRefactoringBundle.message("inline.popup.ignore.conflicts");
case INLINE_ONE -> RefactoringBundle.message("inline.popup.this.only");
case INLINE_ALL_AND_DELETE -> RefactoringBundle.message("inline.popup.all", myAllRefs.size());
case INLINE_ALL_KEEP_OLD_NAME -> RefactoringBundle.message("inline.popup.all.keep", requireNonNull(
PsiUtil.skipParenthesizedExprDown(myVariable.getInitializer())).getText());
case INLINE_ALL_RENAME_INITIALIZER -> RefactoringBundle.message("inline.popup.all.rename", requireNonNull(
PsiUtil.skipParenthesizedExprDown(myVariable.getInitializer())).getText(), myVariable.getName());
default -> throw new IllegalStateException("Unexpected value: " + myMode);
};
}

View File

@@ -7,6 +7,8 @@ import com.intellij.codeInsight.daemon.impl.quickfix.SimplifyBooleanExpressionFi
import com.intellij.codeInspection.dataFlow.JavaMethodContractUtil;
import com.intellij.codeInspection.redundantCast.RemoveRedundantCastUtil;
import com.intellij.java.refactoring.JavaRefactoringBundle;
import com.intellij.modcommand.ActionContext;
import com.intellij.modcommand.ModCommand;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
@@ -24,6 +26,7 @@ import com.intellij.psi.util.PsiPrecedenceUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.RedundantCastUtil;
import com.intellij.refactoring.inline.InlineLocalHandler;
import com.intellij.refactoring.inline.InlineTransformer;
import com.intellij.util.ArrayUtil;
import com.intellij.util.CommonJavaRefactoringUtil;
@@ -895,6 +898,13 @@ public final class InlineUtil implements CommonJavaInlineUtil {
}
}
@Override
public @NotNull ModCommand inline(@NotNull PsiVariable var) {
return InlineLocalHandler.doInline(
ActionContext.from(null, var.getContainingFile()),
var, null, InlineLocalHandler.InlineMode.CHECK_CONFLICTS);
}
public enum TailCallType {
None(null),
Simple((methodCopy, callSite, returnType) -> {

View File

@@ -1,6 +1,7 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.refactoring.util;
import com.intellij.modcommand.ModCommand;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.psi.PsiExpression;
import com.intellij.psi.PsiJavaCodeReferenceElement;
@@ -9,6 +10,12 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public interface CommonJavaInlineUtil {
/**
* @param var variable to inline (local variable or pattern variable)
* @return a modcommand that inlines this variable (may display UI)
*/
@NotNull ModCommand inline(@NotNull PsiVariable var);
static CommonJavaInlineUtil getInstance() {
return ApplicationManager.getApplication().getService(CommonJavaInlineUtil.class);
}

View File

@@ -15,55 +15,30 @@
*/
package com.siyeh.ig.fixes;
import com.intellij.modcommand.ModPsiUpdater;
import com.intellij.modcommand.PsiUpdateModCommandQuickFix;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.modcommand.ModCommand;
import com.intellij.modcommand.ModCommandQuickFix;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiExpression;
import com.intellij.psi.PsiLocalVariable;
import com.intellij.psi.PsiReferenceExpression;
import com.intellij.refactoring.util.CommonJavaInlineUtil;
import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.psiutils.CommentTracker;
import com.siyeh.ig.psiutils.VariableAccessUtils;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collection;
public class InlineVariableFix extends PsiUpdateModCommandQuickFix {
public class InlineVariableFix extends ModCommandQuickFix {
@Override
public @NotNull String getFamilyName() {
return InspectionGadgetsBundle.message("inline.variable.quickfix");
}
@Override
protected void applyFix(@NotNull Project project, @NotNull PsiElement nameElement, @NotNull ModPsiUpdater updater) {
final PsiLocalVariable variable = (PsiLocalVariable)nameElement.getParent();
public @NotNull ModCommand perform(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
final PsiLocalVariable variable = (PsiLocalVariable)descriptor.getPsiElement().getParent();
final PsiExpression initializer = variable.getInitializer();
if (initializer == null) {
return;
}
final Collection<PsiReferenceExpression> references = VariableAccessUtils.getVariableReferences(variable);
final Collection<PsiElement> replacedElements = new ArrayList<>();
for (PsiReferenceExpression reference : references) {
var inlineUtil = CommonJavaInlineUtil.getInstance();
final PsiExpression expression = inlineUtil.inlineVariable(variable, initializer, reference, null);
replacedElements.add(expression);
}
new CommentTracker().deleteAndRestoreComments(variable);
boolean positioned = false;
for (PsiElement element : replacedElements) {
if (element.isValid()) {
updater.highlight(element);
if (!positioned) {
positioned = true;
updater.moveCaretTo(element);
}
}
return ModCommand.nop();
}
var inlineUtil = CommonJavaInlineUtil.getInstance();
return inlineUtil.inline(variable);
}
}

View File

@@ -1,3 +1,4 @@
// "Inline variable" "true-preview"
package com.siyeh.igfixes.inline;
class ArrayInitializer {

View File

@@ -1,3 +1,4 @@
// "Inline variable" "true-preview"
package com.siyeh.igfixes.inline;
class CastNeeded {

View File

@@ -1,3 +1,4 @@
// "Inline variable" "true-preview"
class Comment {
Object x(String s) {

View File

@@ -1,3 +1,4 @@
// "Inline variable" "true-preview"
import java.util.List;
public class Demo {
@@ -5,7 +6,7 @@ public class Demo {
public static void main(String[] args) {
var y = switch (1) {
case 1:
yield <caret>3;
<caret> yield 3;
default:
yield 4;
};

View File

@@ -1,8 +1,9 @@
// "Inline variable" "true-preview"
import java.util.List;
public class Demo {
List<String> test(List<String> list) {
return <caret>list.stream()
<caret> return list.stream()
.map(String::toUpperCase)
.map(String::trim)
.toList();

View File

@@ -1,3 +1,4 @@
// "Inline variable" "true-preview"
class C {
void m() throws Exception {
try (AutoCloseable r1 = null) {

View File

@@ -1,3 +1,4 @@
// "Inline variable" "true-preview"
class C {
void m() throws Exception {
try (AutoCloseable r1 = null; AutoCloseable r3 = null) {

View File

@@ -1,3 +1,4 @@
// "Inline variable" "true-preview"
class C {
void m() throws Exception {
try (AutoCloseable r1 = null) {

View File

@@ -0,0 +1,19 @@
// "Inline variable|->Keep the 'vExposure' variable" "true-preview"
package com.example;
import java.util.ArrayList;
import java.util.List;
public class Demo {
static class ExposureSpecification {
}
public static void main(String[] args) {
List<ExposureSpecification> vExposures = new ArrayList<>();
for (ExposureSpecification vExposure : vExposures) {
// ... lines of code using exp ...
System.out.println(vExposure);
}
}
}

View File

@@ -0,0 +1,19 @@
// "Inline variable|->Inline and rename 'vExposure' to 'exp'" "true-preview"
package com.example;
import java.util.ArrayList;
import java.util.List;
public class Demo {
static class ExposureSpecification {
}
public static void main(String[] args) {
List<ExposureSpecification> vExposures = new ArrayList<>();
for (ExposureSpecification exp : vExposures) {
// ... lines of code using exp ...
System.out.println(exp);
}
}
}

View File

@@ -0,0 +1,20 @@
// "Inline variable|->Inline and rename 'vExposure' to 'exp'" "true-preview"
package com.example;
import java.util.ArrayList;
import java.util.List;
public class Demo {
static class ExposureSpecification {
}
public static void main(String[] args) {
List<ExposureSpecification> vExposures = new ArrayList<>();
for (ExposureSpecification exp : vExposures) {
System.out.println(exp);
// ... lines of code using exp ...
System.out.println(exp);
}
}
}

View File

@@ -1,3 +1,4 @@
// "Inline variable" "true-preview"
package com.siyeh.igfixes.inline;
class ArrayInitializer {

View File

@@ -1,3 +1,4 @@
// "Inline variable" "false"
class Base {
{
Descendant descendant = new Descendant();

View File

@@ -1,3 +1,4 @@
// "Inline variable" "true-preview"
package com.siyeh.igfixes.inline;
class CastNeeded {

View File

@@ -1,3 +1,4 @@
// "Inline variable" "true-preview"
class Comment {
Object x(String s) {

View File

@@ -1,3 +1,4 @@
// "Inline variable" "true-preview"
class C {
void m() throws Exception {
try (AutoCloseable r1 = null; AutoCloseable <caret>r2 = r1) {

View File

@@ -1,3 +1,4 @@
// "Inline variable" "true-preview"
class C {
void m() throws Exception {
try (AutoCloseable r1 = null; AutoCloseable <caret>r2 = r1; AutoCloseable r3 = null) {

View File

@@ -1,3 +1,4 @@
// "Inline variable" "true-preview"
class C {
void m() throws Exception {
try (AutoCloseable r1 = null) {

View File

@@ -0,0 +1,20 @@
// "Inline variable|->Keep the 'vExposure' variable" "true-preview"
package com.example;
import java.util.ArrayList;
import java.util.List;
public class Demo {
static class ExposureSpecification {
}
public static void main(String[] args) {
List<ExposureSpecification> vExposures = new ArrayList<>();
for (ExposureSpecification vExposure : vExposures) {
ExposureSpecification e<caret>xp = vExposure;
// ... lines of code using exp ...
System.out.println(exp);
}
}
}

View File

@@ -0,0 +1,20 @@
// "Inline variable|->Inline and rename 'vExposure' to 'exp'" "true-preview"
package com.example;
import java.util.ArrayList;
import java.util.List;
public class Demo {
static class ExposureSpecification {
}
public static void main(String[] args) {
List<ExposureSpecification> vExposures = new ArrayList<>();
for (ExposureSpecification vExposure : vExposures) {
ExposureSpecification e<caret>xp = vExposure;
// ... lines of code using exp ...
System.out.println(exp);
}
}
}

View File

@@ -0,0 +1,21 @@
// "Inline variable|->Inline and rename 'vExposure' to 'exp'" "true-preview"
package com.example;
import java.util.ArrayList;
import java.util.List;
public class Demo {
static class ExposureSpecification {
}
public static void main(String[] args) {
List<ExposureSpecification> vExposures = new ArrayList<>();
for (ExposureSpecification vExposure : vExposures) {
System.out.println(vExposure);
ExposureSpecification e<caret>xp = vExposure;
// ... lines of code using exp ...
System.out.println(exp);
}
}
}

View File

@@ -1,33 +1,27 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.siyeh.ig.fixes;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.testFramework.builders.JavaModuleFixtureBuilder;
import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.IGQuickFixesTestCase;
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.testFramework.LightProjectDescriptor;
import com.siyeh.ig.dataflow.UnnecessaryLocalVariableInspection;
import org.jetbrains.annotations.NotNull;
public class InlineVariableFixTest extends IGQuickFixesTestCase {
import static com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase.JAVA_21;
public class InlineVariableFixTest extends LightQuickFixParameterizedTestCase {
@Override
public void setUp() throws Exception {
super.setUp();
myFixture.enableInspections(new UnnecessaryLocalVariableInspection());
myRelativePath = "inline";
myDefaultHint = InspectionGadgetsBundle.message("inline.variable.quickfix");
protected LocalInspectionTool @NotNull [] configureLocalInspectionTools() {
return new UnnecessaryLocalVariableInspection[] {new UnnecessaryLocalVariableInspection()};
}
@Override
protected void tuneFixture(final JavaModuleFixtureBuilder builder) throws Exception {
builder.setLanguageLevel(LanguageLevel.JDK_21);
protected @NotNull LightProjectDescriptor getProjectDescriptor() {
return JAVA_21;
}
public void testResourceVar() { doTest(); }
public void testResourceVarInMiddle() { doTest(); }
public void testSingleResourceVar() { doTest(); }
public void testCastNeeded() { doTest(); }
public void testArrayInitializer() { doTest(); }
public void testCastForOverloads() { assertQuickfixNotAvailable(); }
public void testComment() { doTest(); }
public void testNewLineInInitializer() { doTest(); }
public void testInlineInsideSwitchExpression() { doTest(); }
@Override
protected String getBasePath() {
return "/ig/com/siyeh/igfixes/inline";
}
}

View File

@@ -230,6 +230,8 @@ all.references.and.remove.the.local=Inline &all references and remove the variab
this.reference.only.and.keep.the.variable=Inline this reference only and &keep the variable
inline.popup.this.only=This reference only
inline.popup.all=All {0} references and remove the variable
inline.popup.all.keep=Keep the ''{0}'' variable
inline.popup.all.rename=Inline and rename ''{0}'' to ''{1}''
inline.variable.title=Inline Variable
variable.is.referenced.in.multiple.files=Variable {0} is referenced in multiple files
inline.command=Inline {0}