diff --git a/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties b/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties index f917e8b19e7e..17993cb7f7ff 100644 --- a/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties +++ b/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties @@ -618,4 +618,5 @@ inspection.redundant.requires.statement.message.java.base.implicitly.required='j reassign.variable.display.name=Reassigned variable intention.family.name.remove.repeating.call=Remove repeating call intention.name.remove.repeating.call=Remove repeating call ''{0}()'' -intention.family.name.replace.with.expression=Replace with expression \ No newline at end of file +intention.family.name.replace.with.expression=Replace with expression +intention.name.make.variable.effectively.final=Make variable effectively final \ No newline at end of file diff --git a/java/java-analysis-api/src/com/intellij/codeInsight/intention/QuickFixFactory.java b/java/java-analysis-api/src/com/intellij/codeInsight/intention/QuickFixFactory.java index b2e3645879cd..bd8ae3aa3a42 100644 --- a/java/java-analysis-api/src/com/intellij/codeInsight/intention/QuickFixFactory.java +++ b/java/java-analysis-api/src/com/intellij/codeInsight/intention/QuickFixFactory.java @@ -617,4 +617,10 @@ public abstract class QuickFixFactory { public abstract @NotNull IntentionAction createConvertAnonymousToInnerAction(@NotNull PsiAnonymousClass anonymousClass); public abstract @NotNull IntentionAction createSplitSwitchBranchWithSeveralCaseValuesAction(); + + /** + * @param variable variable to make an effectively final + * @return a fix that refactors code to make variable effectively final when possible. Null, if it cannot create such a fix. + */ + public abstract @Nullable IntentionAction createMakeVariableEffectivelyFinalFix(@NotNull PsiVariable variable); } \ No newline at end of file diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightControlFlowUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightControlFlowUtil.java index 79f238079996..a7f4e35c5813 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightControlFlowUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightControlFlowUtil.java @@ -734,6 +734,7 @@ public final class HighlightControlFlowUtil { String text = JavaErrorBundle.message("lambda.variable.must.be.final"); HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(context).descriptionAndTooltip(text).create(); QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createVariableAccessFromInnerClassFix(variable, lambdaExpression)); + QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createMakeVariableEffectivelyFinalFix(variable)); return ErrorFixExtensionPoint.registerFixes(highlightInfo, context, "lambda.variable.must.be.final"); } } diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/MakeVarEffectivelyFinalFix.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/MakeVarEffectivelyFinalFix.java new file mode 100644 index 000000000000..2f82448e5acb --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/MakeVarEffectivelyFinalFix.java @@ -0,0 +1,231 @@ +// 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.codeInsight.daemon.impl.quickfix; + +import com.intellij.codeInsight.BlockUtils; +import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil; +import com.intellij.codeInsight.intention.HighPriorityAction; +import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement; +import com.intellij.java.analysis.JavaAnalysisBundle; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.*; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiUtil; +import com.intellij.util.containers.ContainerUtil; +import com.siyeh.ig.psiutils.ExpressionUtils; +import com.siyeh.ig.psiutils.VariableAccessUtils; +import one.util.streamex.MoreCollectors; +import one.util.streamex.StreamEx; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class MakeVarEffectivelyFinalFix extends LocalQuickFixAndIntentionActionOnPsiElement implements HighPriorityAction { + private MakeVarEffectivelyFinalFix(@NotNull PsiLocalVariable variable) { + super(variable); + } + + @Override + public void invoke(@NotNull Project project, + @NotNull PsiFile file, + @Nullable Editor editor, + @NotNull PsiElement startElement, + @NotNull PsiElement endElement) { + if (!(startElement instanceof PsiLocalVariable local)) return; + EffectivelyFinalFixer fixer = ContainerUtil.find(EffectivelyFinalFixer.values(), f -> f.isAvailable(local)); + if (fixer == null) return; + fixer.fix(local); + } + + @Override + public @NotNull String getText() { + return JavaAnalysisBundle.message("intention.name.make.variable.effectively.final"); + } + + @Override + public @NotNull String getFamilyName() { + return getText(); + } + + public static @Nullable MakeVarEffectivelyFinalFix createFix(@NotNull PsiVariable variable) { + if (!(variable instanceof PsiLocalVariable local)) return null; + if (!ContainerUtil.exists(EffectivelyFinalFixer.values(), f -> f.isAvailable(local))) return null; + return new MakeVarEffectivelyFinalFix(local); + } + + enum EffectivelyFinalFixer { + MOVE_INITIALIZER_TO_IF_BRANCH { + @Override + boolean isAvailable(@NotNull PsiLocalVariable var) { + PsiExpression initializer = var.getInitializer(); + if (!ExpressionUtils.isSafelyRecomputableExpression(initializer)) return false; + if (refersToNonFinalLocal(initializer)) return false; + Branched branched = extractInitMode(var); + // Do not add too many branches + return branched != null && branched.numberOfNonInitializedBranches() <= 3; + } + + @Override + void fix(@NotNull PsiLocalVariable var) { + PsiExpression initializer = Objects.requireNonNull(var.getInitializer()); + Branched branched = Objects.requireNonNull(extractInitMode(var)); + PsiStatement statement = JavaPsiFacade.getElementFactory(var.getProject()) + .createStatementFromText(var.getName() + "=" + initializer.getText() + ";", null); + branched.addInitializer(statement); + initializer.delete(); + } + }; + abstract boolean isAvailable(@NotNull PsiLocalVariable var); + + abstract void fix(@NotNull PsiLocalVariable var); + } + + private static boolean refersToNonFinalLocal(PsiExpression initializer) { + if (initializer instanceof PsiReferenceExpression ref && ref.resolve() instanceof PsiVariable refTarget) { + if (PsiUtil.isJvmLocalVariable(refTarget) && !refTarget.hasModifierProperty(PsiModifier.FINAL)) { + PsiElement block = PsiUtil.getVariableCodeBlock(refTarget, null); + return block == null || !HighlightControlFlowUtil.isEffectivelyFinal(refTarget, block, null); + } + } + return false; + } + + @Nullable + private static Branched extractInitMode(@NotNull PsiLocalVariable var) { + List initializers = initializers(var); + if (initializers.isEmpty()) return null; + if (!(var.getParent() instanceof PsiDeclarationStatement decl)) return null; + if (!(decl.getParent() instanceof PsiCodeBlock block)) return null; + PsiElement commonParent = PsiTreeUtil.findCommonParent(initializers); + PsiIfStatement ifStatement = getTopLevelIfStatement(block, commonParent); + if (ifStatement == null) return null; + InitMode mode = StreamEx.of(initializers) + .map(initStatement -> InitMode.create(initStatement, ifStatement)) + .collect(MoreCollectors.reducingWithZero(ExactMode.BOTTOM, ExactMode.NOT_INITIALIZED, InitMode::join)); + if (!(mode instanceof Branched branched)) return null; + return branched; + } + + @Nullable + private static PsiIfStatement getTopLevelIfStatement(PsiCodeBlock block, PsiElement commonParent) { + while (true) { + PsiIfStatement ifStatement = PsiTreeUtil.getNonStrictParentOfType(commonParent, PsiIfStatement.class); + if (ifStatement == null) return null; + PsiElement parent = ifStatement.getParent(); + if (parent == block) return ifStatement; + commonParent = parent; + } + } + + private static @NotNull List initializers(@NotNull PsiLocalVariable var) { + PsiElement block = PsiUtil.getVariableCodeBlock(var, null); + if (block == null) return List.of(); + List references = VariableAccessUtils.getVariableReferences(var, block); + List initializers = new ArrayList<>(); + for (PsiReferenceExpression reference : references) { + if (!PsiUtil.isAccessedForWriting(reference)) continue; + if (!(reference.getParent() instanceof PsiAssignmentExpression assign) || assign.getOperationTokenType() != JavaTokenType.EQ) { + return List.of(); + } + if (!(assign.getParent() instanceof PsiExpressionStatement statement)) return List.of(); + initializers.add(statement); + } + return initializers; + } + + sealed interface InitMode { + @NotNull InitMode join(@NotNull InitMode nextMode); + + int numberOfNonInitializedBranches(); + + private static InitMode fromInitializer(@NotNull PsiStatement statement, @NotNull InitMode origMode) { + if (statement.getParent() instanceof PsiCodeBlock codeBlock && codeBlock.getParent() instanceof PsiBlockStatement block) { + statement = block; + } + if (statement.getParent() instanceof PsiIfStatement ifStatement) { + if (ifStatement.getThenBranch() == statement) { + return new Branched(ifStatement, origMode, ExactMode.NOT_INITIALIZED); + } else { + return new Branched(ifStatement, ExactMode.NOT_INITIALIZED, origMode); + } + } + return ExactMode.BOTTOM; + } + + static @NotNull InitMode create(@NotNull PsiExpressionStatement statement, @NotNull PsiIfStatement topStatement) { + PsiStatement current = statement; + InitMode curMode = ExactMode.INITIALIZED; + while (current != topStatement) { + curMode = fromInitializer(current, curMode); + if (curMode instanceof Branched branched) { + current = branched.ifStatement(); + } else { + return ExactMode.BOTTOM; + } + } + return curMode; + } + } + + enum ExactMode implements InitMode { + INITIALIZED, NOT_INITIALIZED, BOTTOM; + + @Override + public @NotNull InitMode join(@NotNull InitMode nextMode) { + if (nextMode == NOT_INITIALIZED) return this; + if (this == BOTTOM || this == INITIALIZED) return BOTTOM; + return nextMode; + } + + @Override + public int numberOfNonInitializedBranches() { + return this == NOT_INITIALIZED ? 1 : 0; + } + } + + record Branched(@NotNull PsiIfStatement ifStatement, @NotNull InitMode thenBranch, @NotNull InitMode elseBranch) implements InitMode { + @Override + public @NotNull InitMode join(@NotNull InitMode nextMode) { + if (nextMode == ExactMode.NOT_INITIALIZED) return this; + if (!(nextMode instanceof Branched branched)) return ExactMode.BOTTOM; + if (ifStatement() != branched.ifStatement()) return ExactMode.BOTTOM; + InitMode newThen = thenBranch().join(branched.thenBranch()); + InitMode newElse = elseBranch().join(branched.elseBranch()); + if (newThen == ExactMode.BOTTOM || newElse == ExactMode.BOTTOM) return ExactMode.BOTTOM; + if (newThen == newElse) return newThen; + return new Branched(ifStatement(), newThen, newElse); + } + + @Override + public int numberOfNonInitializedBranches() { + return thenBranch().numberOfNonInitializedBranches() + elseBranch().numberOfNonInitializedBranches(); + } + + void addInitializer(@NotNull PsiStatement statement) { + if (thenBranch() == ExactMode.NOT_INITIALIZED) { + PsiStatement branch = ifStatement().getThenBranch(); + if (branch == null) { + ifStatement().setThenBranch(JavaPsiFacade.getElementFactory(ifStatement().getProject()).createStatementFromText("{}", null)); + branch = Objects.requireNonNull(ifStatement().getThenBranch()); + } + BlockUtils.addBefore(branch, statement); + } else if (thenBranch() instanceof Branched branched) { + branched.addInitializer(statement); + } + if (elseBranch() == ExactMode.NOT_INITIALIZED) { + PsiStatement branch = ifStatement().getElseBranch(); + if (branch == null) { + ifStatement().setElseBranch(JavaPsiFacade.getElementFactory(ifStatement().getProject()).createStatementFromText("{}", null)); + branch = Objects.requireNonNull(ifStatement().getElseBranch()); + } + BlockUtils.addBefore(branch, statement); + } + else if (elseBranch() instanceof Branched branched) { + branched.addInitializer(statement); + } + } + } +} \ No newline at end of file diff --git a/java/java-impl/src/com/intellij/codeInsight/intention/impl/config/QuickFixFactoryImpl.java b/java/java-impl/src/com/intellij/codeInsight/intention/impl/config/QuickFixFactoryImpl.java index 9cf7f6abae46..5c99c4b9176f 100644 --- a/java/java-impl/src/com/intellij/codeInsight/intention/impl/config/QuickFixFactoryImpl.java +++ b/java/java-impl/src/com/intellij/codeInsight/intention/impl/config/QuickFixFactoryImpl.java @@ -1178,4 +1178,9 @@ public final class QuickFixFactoryImpl extends QuickFixFactory { public @NotNull IntentionAction createSplitSwitchBranchWithSeveralCaseValuesAction() { return new SplitSwitchBranchWithSeveralCaseValuesAction(); } + + @Override + public @Nullable IntentionAction createMakeVariableEffectivelyFinalFix(@NotNull PsiVariable variable) { + return MakeVarEffectivelyFinalFix.createFix(variable); + } } diff --git a/java/java-psi-impl/src/com/intellij/codeInsight/BlockUtils.java b/java/java-psi-impl/src/com/intellij/codeInsight/BlockUtils.java index b05578f667ea..48c5c7cc2f4c 100644 --- a/java/java-psi-impl/src/com/intellij/codeInsight/BlockUtils.java +++ b/java/java-psi-impl/src/com/intellij/codeInsight/BlockUtils.java @@ -24,6 +24,17 @@ public final class BlockUtils { */ public static PsiStatement addBefore(PsiStatement anchor, PsiStatement... newStatements) { if (newStatements.length == 0) throw new IllegalArgumentException(); + if (anchor instanceof PsiBlockStatement) { + PsiCodeBlock codeBlock = ((PsiBlockStatement)anchor).getCodeBlock(); + PsiJavaToken brace = codeBlock.getLBrace(); + if (brace != null) { + PsiElement result = brace; + for (PsiStatement statement : newStatements) { + result = codeBlock.addAfter(statement, result); + } + return (PsiStatement)result; + } + } PsiStatement oldStatement = anchor; PsiElement parent = oldStatement.getParent(); while (parent instanceof PsiLabeledStatement) { diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/afterExistingElse.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/afterExistingElse.java new file mode 100644 index 000000000000..7ec2a7f7fe00 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/afterExistingElse.java @@ -0,0 +1,14 @@ +// "Make variable effectively final" "true-preview" +class X { + void test(boolean a) { + int x; + if (a) { + x = 1; + } + else { + x = 0; + System.out.println("hello"); + } + Runnable r = () -> System.out.println(x); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/afterIfElseChain.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/afterIfElseChain.java new file mode 100644 index 000000000000..245e7daccb28 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/afterIfElseChain.java @@ -0,0 +1,18 @@ +// "Make variable effectively final" "true-preview" +class X { + void test(boolean a, boolean b, boolean c) { + int x; + if (a) { + System.out.println("hello"); + x = 1; + } else { + x = 0; + if (b) { + System.out.println("hello2"); + } else if (c) { + System.out.println("hello3"); + } + } + Runnable r = () -> System.out.println(x); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/afterNestedIf.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/afterNestedIf.java new file mode 100644 index 000000000000..823128604b39 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/afterNestedIf.java @@ -0,0 +1,18 @@ +// "Make variable effectively final" "true-preview" +class X { + void test(boolean a, boolean b, boolean c) { + int x; + if (a) { + if (b) { + x = 1; + } else { + x = 2; + } + } else if (c) { + x = 3; + } else { + x = 0; + } + Runnable r = () -> System.out.println(x); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/afterNestedIfSeveralBranches.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/afterNestedIfSeveralBranches.java new file mode 100644 index 000000000000..be003795d72f --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/afterNestedIfSeveralBranches.java @@ -0,0 +1,18 @@ +// "Make variable effectively final" "true-preview" +class X { + void test(boolean a, boolean b, boolean c) { + int x; + if (a) { + if (b) { + x = 1; + } else { + x = 0; + } + } else if (c) { + x = 3; + } else { + x = 0; + } + Runnable r = () -> System.out.println(x); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/afterNestedIfSeveralBranches2.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/afterNestedIfSeveralBranches2.java new file mode 100644 index 000000000000..933eeb0b5571 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/afterNestedIfSeveralBranches2.java @@ -0,0 +1,19 @@ +// "Make variable effectively final" "true-preview" +class X { + void test(boolean a, boolean b, boolean c) { + int x; + if (a) { + x = 0; + System.out.println("hello"); + } else if (b) { + x = 0; + System.out.println("hello2"); + } else if (c) { + x = 0; + System.out.println("hello3"); + } else { + x = 1; + } + Runnable r = () -> System.out.println(x); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/afterSimple.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/afterSimple.java new file mode 100644 index 000000000000..4255129ee3e4 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/afterSimple.java @@ -0,0 +1,12 @@ +// "Make variable effectively final" "true-preview" +class X { + void test(boolean a) { + int x; + if (a) { + x = 1; + } else { + x = 0; + } + Runnable r = () -> System.out.println(x); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/afterVarInitializer.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/afterVarInitializer.java new file mode 100644 index 000000000000..89cfdf3ca59b --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/afterVarInitializer.java @@ -0,0 +1,12 @@ +// "Make variable effectively final" "true-preview" +class X { + void test(boolean a, int y) { + int x; + if (a) { + x = 1; + } else { + x = y; + } + Runnable r = () -> System.out.println(x); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/beforeExistingElse.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/beforeExistingElse.java new file mode 100644 index 000000000000..cb8ef4cff334 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/beforeExistingElse.java @@ -0,0 +1,11 @@ +// "Make variable effectively final" "true-preview" +class X { + void test(boolean a) { + int x = 0; + if (a) { + x = 1; + } + else System.out.println("hello"); + Runnable r = () -> System.out.println(x); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/beforeIfElseChain.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/beforeIfElseChain.java new file mode 100644 index 000000000000..6aee9a8383e6 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/beforeIfElseChain.java @@ -0,0 +1,15 @@ +// "Make variable effectively final" "true-preview" +class X { + void test(boolean a, boolean b, boolean c) { + int x = 0; + if (a) { + System.out.println("hello"); + x = 1; + } else if (b) { + System.out.println("hello2"); + } else if (c) { + System.out.println("hello3"); + } + Runnable r = () -> System.out.println(x); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/beforeNestedIf.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/beforeNestedIf.java new file mode 100644 index 000000000000..99623feb52fe --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/beforeNestedIf.java @@ -0,0 +1,16 @@ +// "Make variable effectively final" "true-preview" +class X { + void test(boolean a, boolean b, boolean c) { + int x = 0; + if (a) { + if (b) { + x = 1; + } else { + x = 2; + } + } else if (c) { + x = 3; + } + Runnable r = () -> System.out.println(x); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/beforeNestedIfSeveralBranches.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/beforeNestedIfSeveralBranches.java new file mode 100644 index 000000000000..8c66e81ead72 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/beforeNestedIfSeveralBranches.java @@ -0,0 +1,14 @@ +// "Make variable effectively final" "true-preview" +class X { + void test(boolean a, boolean b, boolean c) { + int x = 0; + if (a) { + if (b) { + x = 1; + } + } else if (c) { + x = 3; + } + Runnable r = () -> System.out.println(x); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/beforeNestedIfSeveralBranches2.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/beforeNestedIfSeveralBranches2.java new file mode 100644 index 000000000000..1ff1374fc82b --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/beforeNestedIfSeveralBranches2.java @@ -0,0 +1,16 @@ +// "Make variable effectively final" "true-preview" +class X { + void test(boolean a, boolean b, boolean c) { + int x = 0; + if (a) { + System.out.println("hello"); + } else if (b) { + System.out.println("hello2"); + } else if (c) { + System.out.println("hello3"); + } else { + x = 1; + } + Runnable r = () -> System.out.println(x); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/beforeSimple.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/beforeSimple.java new file mode 100644 index 000000000000..c4a36ccaf51a --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/beforeSimple.java @@ -0,0 +1,10 @@ +// "Make variable effectively final" "true-preview" +class X { + void test(boolean a) { + int x = 0; + if (a) { + x = 1; + } + Runnable r = () -> System.out.println(x); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/beforeTooManyBranches.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/beforeTooManyBranches.java new file mode 100644 index 000000000000..2b180970280e --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/beforeTooManyBranches.java @@ -0,0 +1,18 @@ +// "Make variable effectively final" "false" +class X { + void test(boolean a, boolean b, boolean c, boolean d) { + int x = 0; + if (a) { + System.out.println("hello"); + } else if (b) { + System.out.println("hello2"); + } else if (c) { + System.out.println("hello3"); + } else if (d) { + System.out.println("hello4"); + } else { + x = 1; + } + Runnable r = () -> System.out.println(x); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/beforeVarInitializer.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/beforeVarInitializer.java new file mode 100644 index 000000000000..f4f8eb28e82d --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal/beforeVarInitializer.java @@ -0,0 +1,10 @@ +// "Make variable effectively final" "true-preview" +class X { + void test(boolean a, int y) { + int x = y; + if (a) { + x = 1; + } + Runnable r = () -> System.out.println(x); + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/quickfix/MakeVarEffectivelyFinalFixTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/quickfix/MakeVarEffectivelyFinalFixTest.java new file mode 100644 index 000000000000..0f19bbafc81b --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/quickfix/MakeVarEffectivelyFinalFixTest.java @@ -0,0 +1,20 @@ +// 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.codeInsight.daemon.impl.quickfix; + +import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase; +import com.intellij.testFramework.LightProjectDescriptor; +import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase; +import org.jetbrains.annotations.NotNull; + +public class MakeVarEffectivelyFinalFixTest extends LightQuickFixParameterizedTestCase { + @NotNull + @Override + protected LightProjectDescriptor getProjectDescriptor() { + return LightJavaCodeInsightFixtureTestCase.JAVA_17; + } + + @Override + protected String getBasePath() { + return "/codeInsight/daemonCodeAnalyzer/quickFix/makeVarEffectivelyFinal"; + } +}