From 888f4cc38fb5ad2aedf9c4530c3392abf1a0a9d7 Mon Sep 17 00:00:00 2001 From: Georgii Ustinov Date: Wed, 28 Jan 2026 10:11:58 +0100 Subject: [PATCH] [Java] LSP-425 Refactoring IntroduceVariableBase - Create better structure for IntroduceVariableBase#getTempContainer - Use better return type in IntroduceVariableUtil#getIntroduceVariableCandidates GitOrigin-RevId: 3627b2928d974c977a6d89d18ad0f37792b3ba1c --- .../IntroduceFunctionalVariableHandler.java | 7 +- .../IntroduceVariableBase.java | 257 +++++++++--------- .../refactoring/IntroduceVariableUtil.java | 48 ++-- 3 files changed, 166 insertions(+), 146 deletions(-) diff --git a/java/java-impl-refactorings/src/com/intellij/refactoring/introduceVariable/IntroduceFunctionalVariableHandler.java b/java/java-impl-refactorings/src/com/intellij/refactoring/introduceVariable/IntroduceFunctionalVariableHandler.java index e66c6664eb3d..18720d349aa4 100644 --- a/java/java-impl-refactorings/src/com/intellij/refactoring/introduceVariable/IntroduceFunctionalVariableHandler.java +++ b/java/java-impl-refactorings/src/com/intellij/refactoring/introduceVariable/IntroduceFunctionalVariableHandler.java @@ -53,10 +53,11 @@ public class IntroduceFunctionalVariableHandler extends IntroduceVariableHandler } PsiElement anchorStatement = elements[0] instanceof PsiComment ? elements[0] : CommonJavaRefactoringUtil.getParentStatement(elements[0], false); - TempContainerResult result = getTempContainer(anchorStatement); + ErrorOrContainer errorOrContainer = getTempContainer(anchorStatement); - if (result.errorMessage != null) { - showErrorMessage(project, editor, result.errorMessage); + + if (errorOrContainer instanceof ErrorOrContainer.Error(@NlsContexts.DialogMessage String message)) { + showErrorMessage(project, editor, message); } PsiElement[] elementsInCopy = IntroduceParameterHandler.getElementsInCopy(project, file, elements); diff --git a/java/java-impl-refactorings/src/com/intellij/refactoring/introduceVariable/IntroduceVariableBase.java b/java/java-impl-refactorings/src/com/intellij/refactoring/introduceVariable/IntroduceVariableBase.java index 01debe5268e1..6bf7dd91e7d1 100644 --- a/java/java-impl-refactorings/src/com/intellij/refactoring/introduceVariable/IntroduceVariableBase.java +++ b/java/java-impl-refactorings/src/com/intellij/refactoring/introduceVariable/IntroduceVariableBase.java @@ -40,6 +40,7 @@ import com.intellij.refactoring.introduce.inplace.AbstractInplaceIntroducer; import com.intellij.refactoring.introduce.inplace.OccurrencesChooser; import com.intellij.refactoring.introduce.inplace.OccurrencesChooser.ReplaceChoice; import com.intellij.refactoring.introduceField.ElementToWorkOn; +import com.intellij.refactoring.introduceVariable.IntroduceVariableBase.ErrorOrContainer.Container; import com.intellij.refactoring.listeners.RefactoringEventData; import com.intellij.refactoring.listeners.RefactoringEventListener; import com.intellij.refactoring.ui.TypeSelectorManagerImpl; @@ -64,6 +65,9 @@ import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; +import static com.intellij.refactoring.IntroduceVariableUtil.*; +import static com.intellij.refactoring.introduceVariable.IntroduceVariableBase.IntroduceVariableResult.Context; + public abstract class IntroduceVariableBase extends IntroduceHandlerBase { public static class JavaReplaceChoice implements OccurrencesChooser.BaseReplaceChoice { public static final JavaReplaceChoice NO = new JavaReplaceChoice(ReplaceChoice.NO, null, false); @@ -137,14 +141,15 @@ public abstract class IntroduceVariableBase extends IntroduceHandlerBase { final SelectionModel selectionModel = editor.getSelectionModel(); if (!selectionModel.hasSelection()) { final int offset = editor.getCaretModel().getOffset(); - Pair> rangeAndExpressions = getExpressionsAndSelectionRange(project, editor, file, offset); - TextRange suggestedSelection = rangeAndExpressions.getFirst(); + IntroduceVariableCandidates + info = getIntroduceVariableCandidates(project, editor, file, offset); + TextRange suggestedSelection = info.bestRangeToExtractFrom(); if (suggestedSelection != null) { selectionModel.setSelection(suggestedSelection.getStartOffset(), suggestedSelection.getEndOffset()); } else { - final PsiElement[] statementsInRange = IntroduceVariableUtil.findStatementsAtOffset(editor, file, offset); - List expressions = rangeAndExpressions.getSecond(); + final PsiElement[] statementsInRange = findStatementsAtOffset(editor, file, offset); + List expressions = info.expressions(); IntroduceTargetChooser.showChooser(editor, expressions, new Pass<>() { @Override @@ -155,7 +160,7 @@ public abstract class IntroduceVariableBase extends IntroduceHandlerBase { }, new PsiExpressionTrimRenderer.RenderFunction(), RefactoringBundle.message("introduce.target.chooser.expressions.title"), - IntroduceVariableUtil.preferredSelection(statementsInRange, expressions), ScopeHighlighter.NATURAL_RANGER); + preferredSelection(statementsInRange, expressions), ScopeHighlighter.NATURAL_RANGER); return; } } @@ -165,18 +170,25 @@ public abstract class IntroduceVariableBase extends IntroduceHandlerBase { } } + + /** + * @deprecated use {@link IntroduceVariableUtil#getIntroduceVariableCandidates(Project, Editor, PsiFile, int)} instead. + */ + @Deprecated(forRemoval = true) public static @NotNull Pair<@Nullable TextRange, @NotNull List> getExpressionsAndSelectionRange(final @NotNull Project project, - final Editor editor, + final @NotNull Editor editor, final @NotNull PsiFile file, int offset) { - return IntroduceVariableUtil.getExpressionAndSelectionRange(project, editor, file, offset); + IntroduceVariableCandidates + info = getIntroduceVariableCandidates(project, editor, file, offset); + return new Pair<>(info.bestRangeToExtractFrom(), info.expressions()); } private boolean invoke(final Project project, final Editor editor, PsiFile file, int startOffset, int endOffset) { FeatureUsageTracker.getInstance().triggerFeatureUsed(ProductivityFeatureNames.REFACTORING_INTRODUCE_VARIABLE); PsiDocumentManager.getInstance(project).commitAllDocuments(); - return invokeImpl(project, IntroduceVariableUtil.findExpressionInRange(project, file, startOffset, endOffset), editor); + return invokeImpl(project, findExpressionInRange(project, file, startOffset, endOffset), editor); } public @NotNull Pair, List> getPossibleAnchorsAndOccurrences(final Project project, final PsiExpression expr) { @@ -203,13 +215,19 @@ public abstract class IntroduceVariableBase extends IntroduceHandlerBase { private @NotNull OccurrencesInfo buildOccurrencesInfo(Project project, PsiExpression expr) { final PsiElement anchorStatement = getAnchor(expr); - TempContainerResult result = getTempContainer(anchorStatement); + ErrorOrContainer result = getTempContainer(anchorStatement); - if (result.errorMessage != null) { - showErrorMessage(project, null, result.errorMessage); - } + final PsiElement tempContainer = switch (result) { + case Container container -> { + yield container.element(); + } + case ErrorOrContainer.Error error -> { + showErrorMessage(project, null, error.message()); + yield null; + } + }; - final ExpressionOccurrenceManager occurrenceManager = createOccurrenceManager(expr, result.container); + final ExpressionOccurrenceManager occurrenceManager = createOccurrenceManager(expr, tempContainer); final PsiExpression[] occurrences = occurrenceManager.getOccurrences(); return new OccurrencesInfo(occurrences); @@ -232,13 +250,13 @@ public abstract class IntroduceVariableBase extends IntroduceHandlerBase { @Nullable PsiElement targetContainer, @Nullable JavaReplaceChoice replaceChoice, final Editor editor) { - Result result = getIntroduceVariableContext(project, expr, editor); + IntroduceVariableResult introduceVariableResult = getIntroduceVariableContext(project, expr, editor); - switch (result) { + switch (introduceVariableResult) { case Context context -> { return doRefactoring(project, targetContainer, replaceChoice, editor, context); } - case Error error -> { + case IntroduceVariableResult.Error error -> { if (error.message != null) { showErrorMessage(project, editor, error.message); } @@ -303,18 +321,18 @@ public abstract class IntroduceVariableBase extends IntroduceHandlerBase { /** * @return the context necessary for performing "Introduce Variable" refactoring. */ - public static @NotNull Result getIntroduceVariableContext(@NotNull Project project, @Nullable PsiExpression expr, @Nullable Editor editor) { + public static @NotNull IntroduceVariableBase.IntroduceVariableResult getIntroduceVariableContext(@NotNull Project project, @Nullable PsiExpression expr, @Nullable Editor editor) { if (expr != null) { - String message = IntroduceVariableUtil.getErrorMessage(expr); + String message = getErrorMessage(expr); if (message != null) { - return new Error(message); + return new IntroduceVariableResult.Error(message); } PsiExpression topLevelExpression = ExpressionUtils.getTopLevelExpression(expr); if (topLevelExpression.getParent() instanceof PsiField f) { PsiClass containingClass = f.getContainingClass(); if (containingClass != null && containingClass.isInterface()) { message = JavaRefactoringBundle.message("introduce.variable.message.cannot.extract.variable.in.interface"); - return new Error(message); + return new IntroduceVariableResult.Error(message); } } } @@ -322,21 +340,21 @@ public abstract class IntroduceVariableBase extends IntroduceHandlerBase { if (expr != null && expr.getParent() instanceof PsiExpressionStatement) { FeatureUsageTracker.getInstance().triggerFeatureUsed("refactoring.introduceVariable.incompleteStatement"); } - if (IntroduceVariableUtil.LOG.isDebugEnabled()) { - IntroduceVariableUtil.LOG.debug("expression:" + expr); + if (LOG.isDebugEnabled()) { + LOG.debug("expression:" + expr); } if (expr == null || !expr.isPhysical()) { - if (ReassignVariableUtil.reassign(editor)) return new Error(null); + if (ReassignVariableUtil.reassign(editor)) return new IntroduceVariableResult.Error(null); if (expr == null) { String message = JavaRefactoringBundle.message("selected.block.should.represent.an.expression"); - return new Error(message); + return new IntroduceVariableResult.Error(message); } } String enumInSwitchError = RefactoringUtil.checkEnumConstantInSwitchLabel(expr); if (enumInSwitchError != null) { - return new Error(enumInSwitchError); + return new IntroduceVariableResult.Error(enumInSwitchError); } @@ -345,12 +363,12 @@ public abstract class IntroduceVariableBase extends IntroduceHandlerBase { dumbService.computeWithAlternativeResolveEnabled(() -> CommonJavaRefactoringUtil.getTypeByExpressionWithExpectedType(expr)); if (originalType == null || LambdaUtil.notInferredType(originalType)) { String message = JavaRefactoringBundle.message("unknown.expression.type"); - return new Error(message); + return new IntroduceVariableResult.Error(message); } if (PsiTypes.voidType().equals(originalType)) { String message = JavaRefactoringBundle.message("selected.expression.has.void.type"); - return new Error(message); + return new IntroduceVariableResult.Error(message); } try { @@ -360,51 +378,53 @@ public abstract class IntroduceVariableBase extends IntroduceHandlerBase { } catch (IncorrectOperationException ignore) { String message = JavaRefactoringBundle.message("unknown.expression.type"); - return new Error(message); + return new IntroduceVariableResult.Error(message); } for (PsiPatternVariable variable : JavaPsiPatternUtil.getExposedPatternVariables(expr)) { if (ContainerUtil.exists(VariableAccessUtils.getVariableReferences(variable), ref -> !PsiTreeUtil.isAncestor(expr, ref, true))) { String message = JavaRefactoringBundle.message("selected.expression.introduces.pattern.variable", variable.getName()); - return new Error(message); + return new IntroduceVariableResult.Error(message); } } final PsiElement anchorStatement = getAnchor(expr); - TempContainerResult result = getTempContainer(anchorStatement); - if (result.errorMessage != null) { - return new Error(result.errorMessage, false); - } + ErrorOrContainer errorOrContainer = getTempContainer(anchorStatement); - final PsiElement tempContainer = Objects.requireNonNull(result.container); + return switch (errorOrContainer) { + case Container container -> { + final PsiFile file = Objects.requireNonNull(anchorStatement).getContainingFile(); + LOG.assertTrue(file != null, "expr.getContainingFile() == null"); + final PsiElement nameSuggestionContext = editor == null ? null : file.findElementAt(editor.getCaretModel().getOffset()); + final RefactoringSupportProvider supportProvider = LanguageRefactoringSupport.getInstance().forContext(expr); + final boolean isInplaceAvailableOnDataContext = + supportProvider != null && + editor != null && + editor.getSettings().isVariableInplaceRenameEnabled() && + supportProvider.isInplaceIntroduceAvailable(expr, nameSuggestionContext) && + !isInJspHolderMethod(expr); - final PsiFile file = Objects.requireNonNull(anchorStatement).getContainingFile(); - IntroduceVariableUtil.LOG.assertTrue(file != null, "expr.getContainingFile() == null"); - final PsiElement nameSuggestionContext = editor == null ? null : file.findElementAt(editor.getCaretModel().getOffset()); - final RefactoringSupportProvider supportProvider = LanguageRefactoringSupport.getInstance().forContext(expr); - final boolean isInplaceAvailableOnDataContext = - supportProvider != null && - editor != null && - editor.getSettings().isVariableInplaceRenameEnabled() && - supportProvider.isInplaceIntroduceAvailable(expr, nameSuggestionContext) && - !isInJspHolderMethod(expr); + if (isInplaceAvailableOnDataContext) { + final MultiMap conflicts = new MultiMap<>(); + checkInLoopCondition(expr, conflicts); + if (!conflicts.isEmpty()) { + yield new IntroduceVariableResult.Error(StringUtil.join(new TreeSet<>(conflicts.values()), "
"), false); + } + } - if (isInplaceAvailableOnDataContext) { - final MultiMap conflicts = new MultiMap<>(); - checkInLoopCondition(expr, conflicts); - if (!conflicts.isEmpty()) { - return new Error(StringUtil.join(new TreeSet<>(conflicts.values()), "
"), false); + final ExpressionOccurrenceManager occurrenceManager = createOccurrenceManager(expr, container.element()); + final PsiExpression[] occurrences = occurrenceManager.getOccurrences(); + + OccurrencesInfo occurrencesInfo = new OccurrencesInfo(occurrences); + + yield new Context(expr, originalType, anchorStatement, occurrenceManager, occurrencesInfo, isInplaceAvailableOnDataContext); } - } - - final ExpressionOccurrenceManager occurrenceManager = createOccurrenceManager(expr, tempContainer); - final PsiExpression[] occurrences = occurrenceManager.getOccurrences(); - - OccurrencesInfo occurrencesInfo = new OccurrencesInfo(occurrences); - - return new Context(expr, originalType, anchorStatement, occurrenceManager, occurrencesInfo, isInplaceAvailableOnDataContext); + case ErrorOrContainer.Error error -> { + yield new IntroduceVariableResult.Error(error.message(), false); + } + }; } private static @Nullable PsiType getNormalizedType(PsiExpression expr) { @@ -458,38 +478,24 @@ public abstract class IntroduceVariableBase extends IntroduceHandlerBase { return physicalElement != null ? physicalElement : place; } - - /** - * Contract of the class: when {@code errorMessage} is not null, {@code container} must be null and vice versa. - */ - protected static class TempContainerResult { - final @Nullable @NlsContexts.DialogMessage String errorMessage; - final @Nullable PsiElement container; - - TempContainerResult(@Nullable @NlsContexts.DialogMessage String errorMessage, @Nullable PsiElement container) { - this.errorMessage = errorMessage; - this.container = container; - } - } - - protected static @NotNull TempContainerResult getTempContainer(@Nullable PsiElement anchorStatement) { + protected static @NotNull ErrorOrContainer getTempContainer(@Nullable PsiElement anchorStatement) { if (anchorStatement == null) { - return new TempContainerResult( - JavaRefactoringBundle.message("refactoring.is.not.supported.in.the.current.context", getRefactoringName()), null + return new ErrorOrContainer.Error( + JavaRefactoringBundle.message("refactoring.is.not.supported.in.the.current.context", getRefactoringName()) ); } String anchorMessage = getAnchorBeforeMessage(anchorStatement); - if (anchorMessage != null) return new TempContainerResult(anchorMessage, null); + if (anchorMessage != null) return new ErrorOrContainer.Error(anchorMessage); final PsiElement tempContainer = anchorStatement.getParent(); if (!(tempContainer instanceof PsiCodeBlock) && !CommonJavaRefactoringUtil.isLoopOrIf(tempContainer) && !(tempContainer instanceof PsiLambdaExpression) && (tempContainer.getParent() instanceof PsiLambdaExpression)) { - return new TempContainerResult( - JavaRefactoringBundle.message("refactoring.is.not.supported.in.the.current.context", getRefactoringName()), null + return new ErrorOrContainer.Error( + JavaRefactoringBundle.message("refactoring.is.not.supported.in.the.current.context", getRefactoringName()) ); } - return new TempContainerResult(null, tempContainer); + return new Container(tempContainer); } private static ExpressionOccurrenceManager createOccurrenceManager(PsiExpression expr, @Nullable PsiElement tempContainer) { @@ -845,7 +851,7 @@ public abstract class IntroduceVariableBase extends IntroduceHandlerBase { .map(place -> (PsiExpression)getPhysicalElement(place)) .groupingBy(e -> PsiTreeUtil.findCommonParent(e, physical), () -> new TreeMap<>(treeOrder), Collectors.toList()); - IntroduceVariableUtil.LOG.assertTrue(!groupByBlock.isEmpty()); + LOG.assertTrue(!groupByBlock.isEmpty()); List currentOccurrences = new ArrayList<>(); Map counts = new HashMap<>(); groupByBlock.forEach((parent, occurrences) -> { @@ -972,7 +978,7 @@ public abstract class IntroduceVariableBase extends IntroduceHandlerBase { .map(Function.identity()) .pairMap((prev, next) -> text.substring(prev.getKey(), next.getKey()) + next.getValue()) .joining(); - IntroduceVariableUtil.LOG.error("Unable to find anchor for a new variable; selectedOccurrences.length = " + selectedOccurrences.length, + LOG.error("Unable to find anchor for a new variable; selectedOccurrences.length = " + selectedOccurrences.length, new Attachment("source.java", textWithOccurrences)); return; } @@ -1030,9 +1036,9 @@ public abstract class IntroduceVariableBase extends IntroduceHandlerBase { chosenAnchor instanceof PsiSwitchLabelStatementBase; Consumer callback = container -> { PsiElement anchor = container instanceof PsiLambdaExpression ? getAnchor(container) : container; - TempContainerResult result = getTempContainer(anchor); - if (result.errorMessage != null) { - showErrorMessage(project, editor, result.errorMessage); + ErrorOrContainer errorOrContainer = getTempContainer(anchor); + if (errorOrContainer instanceof ErrorOrContainer.Error(String message)) { + showErrorMessage(project, editor, message); return; } myInplaceIntroducer = new JavaVariableInplaceIntroducer(project, settings, anchor, editor, expr, @@ -1052,56 +1058,61 @@ public abstract class IntroduceVariableBase extends IntroduceHandlerBase { } } - /** * Represents the result of getting the necessary environment for introducing a variable. - * @see IntroduceVariablePass#getIntroduceVariableContext(Project, PsiFile, Editor, TextRange) + * @see IntroduceVariablePass#getIntroduceVariableContext(Project, PsiExpression, Editor) */ - public sealed interface Result permits Error, Context {} + public sealed interface IntroduceVariableResult permits IntroduceVariableResult.Error, Context { - /** - * Represents a message that will be displayed in UI if there is an error during collecting the context for introduced variable. - * @see IntroduceVariablePass#showErrorMessage(Project, Editor, String) - */ - public static final class Error implements Result { - public final @NlsContexts.DialogMessage @Nullable String message; + /** + * Represents a message that will be displayed in UI if there is an error during collecting the context for introduced variable. + * @see IntroduceVariablePass#showErrorMessage(Project, Editor, String) + */ + final class Error implements IntroduceVariableResult { + public final @NlsContexts.DialogMessage @Nullable String message; - Error(@NlsContexts.DialogMessage @Nullable String message) { - this(message, true); + Error(@NlsContexts.DialogMessage @Nullable String message) { + this(message, true); + } + + Error(@NlsContexts.DialogMessage @Nullable String message, boolean shouldWrap) { + if (message == null) { + this.message = null; + } + else if (shouldWrap) { + this.message = RefactoringBundle.getCannotRefactorMessage(message); + } + else { + this.message = message; + } + } } - Error(@NlsContexts.DialogMessage @Nullable String message, boolean shouldWrap) { - if (message == null) { - this.message = null; - } - else if (shouldWrap) { - this.message = RefactoringBundle.getCannotRefactorMessage(message); - } - else { - this.message = message; + /** + * Represents all the data necessary to introduce the variable. + * @param expression - element that should be extracted into the separate variable. + * @param originalType - type of the expression that should be extracted. + * @param anchorStatement - statement near which the declared variable will be created. + * @param occurrenceManager - stores all occurrences of the expression that should be extracted. + * @param occurrencesInfo - stores additional information about occurrences like whether they are valid for extraction. + * @param isInplaceAvailableOnDataContext - indicates whether inplace refactoring is available for the current context. + */ + record Context( + @NotNull PsiExpression expression, + @NotNull PsiType originalType, + @NotNull PsiElement anchorStatement, + @NotNull ExpressionOccurrenceManager occurrenceManager, + @NotNull OccurrencesInfo occurrencesInfo, + boolean isInplaceAvailableOnDataContext + ) implements IntroduceVariableResult { + PsiFile file() { + return anchorStatement.getContainingFile(); } } } - /** - * Represents all the data necessary to introduce the variable. - * @param expression - element that should be extracted into the separate variable. - * @param originalType - type of the expression that should be extracted. - * @param anchorStatement - statement near which the declared variable will be created. - * @param occurrenceManager - stores all occurrences of the expression that should be extracted. - * @param occurrencesInfo - stores additional information about occurrences like whether they are valid for extraction. - * @param isInplaceAvailableOnDataContext - indicates whether inplace refactoring is available for the current context. - */ - public record Context( - @NotNull PsiExpression expression, - @NotNull PsiType originalType, - @NotNull PsiElement anchorStatement, - @NotNull ExpressionOccurrenceManager occurrenceManager, - @NotNull OccurrencesInfo occurrencesInfo, - boolean isInplaceAvailableOnDataContext - ) implements Result { - PsiFile file() { - return anchorStatement.getContainingFile(); - } + protected sealed interface ErrorOrContainer permits Container, ErrorOrContainer.Error { + record Container(@NotNull PsiElement element) implements ErrorOrContainer {} + record Error(@NotNull @NlsContexts.DialogMessage String message) implements ErrorOrContainer {} } } diff --git a/java/java-impl/src/com/intellij/refactoring/IntroduceVariableUtil.java b/java/java-impl/src/com/intellij/refactoring/IntroduceVariableUtil.java index cde8a25f6163..271971fb029d 100644 --- a/java/java-impl/src/com/intellij/refactoring/IntroduceVariableUtil.java +++ b/java/java-impl/src/com/intellij/refactoring/IntroduceVariableUtil.java @@ -22,7 +22,6 @@ import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.NlsContexts; -import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; @@ -90,22 +89,23 @@ public final class IntroduceVariableUtil { } /** - * @see IntroduceVariableUtil#getExpressionAndSelectionRange(Project, Document, PsiFile, int) + * @see IntroduceVariableUtil#getIntroduceVariableCandidates(Project, Document, PsiFile, int) */ - public static @NotNull Pair<@Nullable TextRange, @NotNull List> getExpressionAndSelectionRange( + public static @NotNull IntroduceVariableCandidates getIntroduceVariableCandidates( final @NotNull Project project, final @NotNull Editor editor, final @NotNull PsiFile file, int offset ) { - return getExpressionAndSelectionRange(project, editor.getDocument(), file, offset); + return getIntroduceVariableCandidates(project, editor.getDocument(), file, offset); } /** - * Searches for the expressions that can be extracted into the variable near the given {@code offset} - * @return {@link TextRange} that includes preferred expression to extract and list of {@link PsiExpression} - candidates that can be extracted as well + * @return the expressions that can be extracted into the variable near the given {@code offset} + * and the recommended {@link TextRange} to select the expression from (could be null). + * @see IntroduceVariableCandidates */ - public static @NotNull Pair<@Nullable TextRange, @NotNull List> getExpressionAndSelectionRange( + public static @NotNull IntroduceVariableCandidates getIntroduceVariableCandidates( final @NotNull Project project, final @NotNull Document document, final @NotNull PsiFile file, @@ -121,7 +121,7 @@ public final class IntroduceVariableUtil { final PsiExpression expressionInRange = findExpressionInRange(project, file, lineRange.getStartOffset(), lineRange.getEndOffset()); if (expressionInRange != null && getErrorMessage(expressionInRange) == null) { - return Pair.create(lineRange, Collections.singletonList(expressionInRange)); + return new IntroduceVariableCandidates(lineRange, Collections.singletonList(expressionInRange)); } } @@ -130,13 +130,13 @@ public final class IntroduceVariableUtil { CommonJavaRefactoringUtil.getParentStatement(expression, false) != null || PsiTreeUtil.getParentOfType(expression, PsiField.class, true, PsiStatement.class) != null); if (expressions.isEmpty()) { - return Pair.create(lineRange, Collections.emptyList()); + return new IntroduceVariableCandidates(lineRange, Collections.emptyList()); } else if (!isChooserNeeded(expressions)) { - return Pair.create(expressions.get(0).getTextRange(), expressions); + return new IntroduceVariableCandidates(expressions.getFirst().getTextRange(), expressions); } else { - return Pair.create(null, expressions); + return new IntroduceVariableCandidates(null, expressions); } } @@ -144,23 +144,24 @@ public final class IntroduceVariableUtil { /** * @return single expression that can be extracted into the variable. */ - public static PsiExpression findExpressionInRange(Project project, PsiFile file, int startOffset, int endOffset) { + public static PsiExpression findExpressionInRange(@NotNull Project project, @NotNull PsiFile file, int startOffset, int endOffset) { PsiExpression tempExpr = CodeInsightFrontbackUtil.findExpressionInRange(file, startOffset, endOffset); if (tempExpr == null) { PsiElement[] statements = CodeInsightFrontbackUtil.findStatementsInRange(file, startOffset, endOffset); if (statements.length == 1) { - if (statements[0] instanceof PsiExpressionStatement) { - tempExpr = ((PsiExpressionStatement) statements[0]).getExpression(); + PsiElement statement = statements[0]; + if (statement instanceof PsiExpressionStatement expressionStatement) { + tempExpr = expressionStatement.getExpression(); } - else if (statements[0] instanceof PsiReturnStatement) { - tempExpr = ((PsiReturnStatement)statements[0]).getReturnValue(); + else if (statement instanceof PsiReturnStatement returnStatement) { + tempExpr = returnStatement.getReturnValue(); } - else if (statements[0] instanceof PsiSwitchStatement) { - PsiExpression expr = JavaPsiFacade.getElementFactory(project).createExpressionFromText(statements[0].getText(), statements[0]); - TextRange range = statements[0].getTextRange(); + else if (statement instanceof PsiSwitchStatement) { + PsiExpression expr = JavaPsiFacade.getElementFactory(project).createExpressionFromText(statement.getText(), statement); + TextRange range = statement.getTextRange(); final RangeMarker rangeMarker = file.getViewProvider().getDocument().createRangeMarker(range); expr.putUserData(ElementToWorkOn.TEXT_RANGE, rangeMarker); - expr.putUserData(ElementToWorkOn.PARENT, statements[0]); + expr.putUserData(ElementToWorkOn.PARENT, statement); return expr; } } @@ -586,4 +587,11 @@ public final class IntroduceVariableUtil { return parent.replace(createReplacement(ref.getText(), project, prefix, suffix, parent, textRange, new int[1])); } } + + /** + * Stores information about expressions that can be extracted into the variable near the cursor position. + * @param bestRangeToExtractFrom {@link TextRange} that includes the most appropriate expression to be extracted. + * @param expressions list of expressions that can be extracted into variable. + */ + public record IntroduceVariableCandidates(@Nullable TextRange bestRangeToExtractFrom, @NotNull List<@NotNull PsiExpression> expressions) {} }