[java-inspections] MakeVarEffectivelyFinalFix: stream API fixer

GitOrigin-RevId: e6f1c6e2d80af6332bae642391ca4b6113aa12ed
This commit is contained in:
Tagir Valeev
2022-10-05 14:59:16 +02:00
committed by intellij-monorepo-bot
parent b95b63e818
commit d9193529e2
36 changed files with 277 additions and 37 deletions

View File

@@ -0,0 +1,68 @@
// 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.codeInspection.streamMigration;
import com.intellij.codeInsight.ExceptionUtil;
import com.intellij.codeInsight.daemon.impl.quickfix.makefinal.EffectivelyFinalFixer;
import com.intellij.java.JavaBundle;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.containers.ContainerUtil;
import com.siyeh.ig.psiutils.ControlFlowUtils;
import com.siyeh.ig.psiutils.VariableAccessUtils;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public final class ConvertToStreamFixer implements EffectivelyFinalFixer {
@Override
public boolean isAvailable(@NotNull PsiLocalVariable var) {
return createModel(var) != null;
}
@Override
public void fix(@NotNull PsiLocalVariable var) {
StreamModel model = createModel(var);
if (model == null) return;
PsiElement element = model.migration().migrate(var.getProject(), model.body(), model.tb());
MigrateToStreamFix.simplify(var.getProject(), element);
}
@Override
public String getText(@NotNull PsiLocalVariable var) {
return JavaBundle.message("intention.make.final.fixer.stream", var.getName());
}
private static StreamModel createModel(PsiLocalVariable var) {
PsiElement block = PsiUtil.getVariableCodeBlock(var, null);
if (block == null) return null;
List<PsiReferenceExpression> references = VariableAccessUtils.getVariableReferences(var, block);
List<PsiReferenceExpression> writes = ContainerUtil.filter(references, PsiUtil::isAccessedForWriting);
if (writes.isEmpty()) return null;
PsiElement commonParent = PsiTreeUtil.findCommonParent(writes);
if (commonParent == null) return null;
while (commonParent.getParent() != block) {
commonParent = commonParent.getParent();
if (commonParent == null) return null;
}
if (!(commonParent instanceof PsiLoopStatement statement)) return null;
final PsiStatement body = statement.getBody();
if (body == null) return null;
StreamApiMigrationInspection.StreamSource source = StreamApiMigrationInspection.StreamSource.tryCreate(statement);
if (source == null) return null;
if (!ExceptionUtil.getThrownCheckedExceptions(body).isEmpty()) return null;
TerminalBlock tb = TerminalBlock.from(source, body);
BaseStreamApiMigration migration = StreamApiMigrationInspection.findMigration(statement, body, tb, false, false);
if (migration == null) return null;
ControlFlowUtils.InitializerUsageStatus status = ControlFlowUtils.getInitializerUsageStatus(var, statement);
if (status != ControlFlowUtils.InitializerUsageStatus.DECLARED_JUST_BEFORE &&
status != ControlFlowUtils.InitializerUsageStatus.AT_WANTED_PLACE_ONLY) {
return null;
}
return new StreamModel(migration, body, tb);
}
private record StreamModel(BaseStreamApiMigration migration, PsiStatement body, TerminalBlock tb) {
}
}

View File

@@ -129,6 +129,8 @@
<extensionPoint qualifiedName="com.intellij.jreProvider" interface="com.intellij.execution.ui.JreProvider" dynamic="true"/>
<extensionPoint qualifiedName="com.intellij.sdkEditorAdditionalOptionsProvider" interface="com.intellij.openapi.SdkEditorAdditionalOptionsProvider" dynamic="true"/>
<extensionPoint qualifiedName="com.intellij.exceptionFilter" interface="com.intellij.execution.filters.ExceptionFilterFactory" dynamic="true"/>
<extensionPoint qualifiedName="com.intellij.java.effectively.final.fixer"
interface="com.intellij.codeInsight.daemon.impl.quickfix.makefinal.EffectivelyFinalFixer" dynamic="true"/>
<extensionPoint qualifiedName="com.intellij.compiler.buildTargetScopeProvider"
interface="com.intellij.compiler.impl.BuildTargetScopeProvider" dynamic="true"/>
@@ -2356,6 +2358,8 @@
<registryKey defaultValue="true" description="Show no usages in Java reference code lenses" key="code.lens.java.show.0.usages"/>
<debugger.dfaAssistProvider language="JAVA" implementationClass="com.intellij.debugger.engine.dfaassist.java.JavaDfaAssistProvider"/>
<dataflowIRProvider language="JAVA" implementationClass="com.intellij.codeInspection.dataFlow.java.JavaDataFlowIRProvider"/>
<java.effectively.final.fixer implementation="com.intellij.codeInsight.daemon.impl.quickfix.makefinal.MoveInitializerToIfBranchFixer"/>
<java.effectively.final.fixer implementation="com.intellij.codeInspection.streamMigration.ConvertToStreamFixer"/>
</extensions>
<extensions defaultExtensionNs="org.jetbrains">

View File

@@ -0,0 +1,36 @@
// 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.makefinal;
import com.intellij.codeInspection.util.IntentionName;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.psi.PsiLocalVariable;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
/**
* A fix to make variable effectively final
*/
@ApiStatus.Internal
public
interface EffectivelyFinalFixer {
ExtensionPointName<EffectivelyFinalFixer> EP_NAME = ExtensionPointName.create("com.intellij.java.effectively.final.fixer");
/**
* @param var variable to fix
* @return true if current fix can convert the variable to effectively final
*/
boolean isAvailable(@NotNull PsiLocalVariable var);
/**
* Performs fix
*
* @param var variable to fix
*/
void fix(@NotNull PsiLocalVariable var);
/**
* @param var variable to fix
* @return human-readable name of the fix
*/
@IntentionName String getText(@NotNull PsiLocalVariable var);
}

View File

@@ -12,8 +12,11 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class MakeVarEffectivelyFinalFix extends LocalQuickFixAndIntentionActionOnPsiElement implements HighPriorityAction {
private MakeVarEffectivelyFinalFix(@NotNull PsiLocalVariable variable) {
@SafeFieldForPreview private final @NotNull EffectivelyFinalFixer myFixer;
private MakeVarEffectivelyFinalFix(@NotNull PsiLocalVariable variable, @NotNull EffectivelyFinalFixer fixer) {
super(variable);
myFixer = fixer;
}
@Override
@@ -23,34 +26,25 @@ public class MakeVarEffectivelyFinalFix extends LocalQuickFixAndIntentionActionO
@NotNull PsiElement startElement,
@NotNull PsiElement endElement) {
if (!(startElement instanceof PsiLocalVariable local)) return;
EffectivelyFinalFixer fixer = ContainerUtil.find(FIXERS, f -> f.isAvailable(local));
if (fixer == null) return;
fixer.fix(local);
if (!myFixer.isAvailable(local)) return;
myFixer.fix(local);
}
@Override
public @NotNull String getText() {
return JavaAnalysisBundle.message("intention.name.make.variable.effectively.final");
if (!(getStartElement() instanceof PsiLocalVariable local)) return getFamilyName();
return myFixer.getText(local);
}
@Override
public @NotNull String getFamilyName() {
return getText();
return JavaAnalysisBundle.message("intention.name.make.variable.effectively.final");
}
public static @Nullable MakeVarEffectivelyFinalFix createFix(@NotNull PsiVariable variable) {
if (!(variable instanceof PsiLocalVariable local)) return null;
if (!ContainerUtil.exists(FIXERS, f -> f.isAvailable(local))) return null;
return new MakeVarEffectivelyFinalFix(local);
}
static final EffectivelyFinalFixer[] FIXERS = {
new MoveInitializerToIfBranchFixer()
};
sealed interface EffectivelyFinalFixer permits MoveInitializerToIfBranchFixer {
boolean isAvailable(@NotNull PsiLocalVariable var);
void fix(@NotNull PsiLocalVariable var);
EffectivelyFinalFixer fixer = ContainerUtil.find(EffectivelyFinalFixer.EP_NAME.getExtensionList(), f -> f.isAvailable(local));
if (fixer == null) return null;
return new MakeVarEffectivelyFinalFix(local, fixer);
}
}

View File

@@ -3,6 +3,7 @@ package com.intellij.codeInsight.daemon.impl.quickfix.makefinal;
import com.intellij.codeInsight.BlockUtils;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil;
import com.intellij.java.JavaBundle;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
@@ -40,6 +41,11 @@ final class MoveInitializerToIfBranchFixer implements EffectivelyFinalFixer {
initializer.delete();
}
@Override
public String getText(@NotNull PsiLocalVariable var) {
return JavaBundle.message("intention.make.final.fixer.if", var.getName());
}
private static boolean canReorder(PsiExpression initializer, Branched branched) {
if (ExpressionUtils.isSafelyRecomputableExpression(initializer) && !refersToNonFinalLocal(initializer)) return true;
if (SideEffectChecker.mayHaveSideEffects(initializer)) return false;

View File

@@ -1,4 +1,4 @@
// "Make variable effectively final" "true-preview"
// "Make 'x' effectively final by moving initializer to the 'if' statement" "true-preview"
class X {
void test(boolean a) {
int x;

View File

@@ -1,4 +1,4 @@
// "Make variable effectively final" "true-preview"
// "Make 'x' effectively final by moving initializer to the 'if' statement" "true-preview"
class X {
void test(boolean a, boolean b, boolean c) {
int x;

View File

@@ -1,4 +1,4 @@
// "Make variable effectively final" "true-preview"
// "Make 'x' effectively final by moving initializer to the 'if' statement" "true-preview"
class X {
void test(boolean a, int b, int c) {
int x;

View File

@@ -1,4 +1,4 @@
// "Make variable effectively final" "true-preview"
// "Make 'x' effectively final by moving initializer to the 'if' statement" "true-preview"
class X {
void test(boolean a, boolean b, boolean c) {
int x;

View File

@@ -1,4 +1,4 @@
// "Make variable effectively final" "true-preview"
// "Make 'x' effectively final by moving initializer to the 'if' statement" "true-preview"
class X {
void test(boolean a, boolean b, boolean c) {
int x;

View File

@@ -1,4 +1,4 @@
// "Make variable effectively final" "true-preview"
// "Make 'x' effectively final by moving initializer to the 'if' statement" "true-preview"
class X {
void test(boolean a, boolean b, boolean c) {
int x;

View File

@@ -1,4 +1,4 @@
// "Make variable effectively final" "true-preview"
// "Make 'x' effectively final by moving initializer to the 'if' statement" "true-preview"
class X {
void test(boolean a) {
int x;

View File

@@ -0,0 +1,9 @@
// "Make 'hasEmpty' effectively final using stream API" "true-preview"
import java.util.*;
class X {
void test(List<String> list) {
boolean hasEmpty = list.stream().anyMatch(String::isEmpty);
Runnable r = () -> System.out.println(hasEmpty);
}
}

View File

@@ -0,0 +1,9 @@
// "Make 'x' effectively final using stream API" "true-preview"
import java.util.*;
class Test {
public static void main(String[] args) {
int x = (int) Arrays.stream(args).filter(arg -> !arg.isEmpty()).count();
Runnable r = () -> System.out.println(x);
}
}

View File

@@ -0,0 +1,9 @@
// "Make 's' effectively final using stream API" "true-preview"
import java.util.*;
class Test {
public static void main(String[] args) {
String s = Arrays.stream(args).filter(arg -> arg.length() > 5).findFirst().orElse(null);
Runnable r = () -> System.out.println(s);
}
}

View File

@@ -0,0 +1,9 @@
// "Make 'x' effectively final using stream API" "true-preview"
import java.util.*;
class Test {
public static void main(String[] args) {
int x = Arrays.stream(args).mapToInt(String::length).filter(arg -> arg >= 0).max().orElse(0);
Runnable r = () -> System.out.println(x);
}
}

View File

@@ -0,0 +1,9 @@
// "Make 'x' effectively final using stream API" "true-preview"
import java.util.*;
class Test {
public static void main(String[] args) {
int x = Arrays.stream(args).mapToInt(String::length).sum();
Runnable r = () -> System.out.println(x);
}
}

View File

@@ -1,4 +1,4 @@
// "Make variable effectively final" "true-preview"
// "Make 'x' effectively final by moving initializer to the 'if' statement" "true-preview"
class X {
void test(boolean a, int y) {
int x;

View File

@@ -1,4 +1,4 @@
// "Make variable effectively final" "true-preview"
// "Make 'x' effectively final by moving initializer to the 'if' statement" "true-preview"
class X {
void test(boolean a) {
int x = 0;

View File

@@ -1,4 +1,4 @@
// "Make variable effectively final" "true-preview"
// "Make 'x' effectively final by moving initializer to the 'if' statement" "true-preview"
class X {
void test(boolean a, boolean b, boolean c) {
int x = 0;

View File

@@ -1,4 +1,4 @@
// "Make variable effectively final" "true-preview"
// "Make 'x' effectively final by moving initializer to the 'if' statement" "true-preview"
class X {
void test(boolean a, int b, int c) {
int x = b * c;

View File

@@ -1,4 +1,4 @@
// "Make variable effectively final" "false"
// "Make 'x' effectively final by moving initializer to the 'if' statement" "false"
class X {
void test(boolean a, int b, int c) {
int x = b * c;

View File

@@ -1,4 +1,4 @@
// "Make variable effectively final" "true-preview"
// "Make 'x' effectively final by moving initializer to the 'if' statement" "true-preview"
class X {
void test(boolean a, boolean b, boolean c) {
int x = 0;

View File

@@ -1,4 +1,4 @@
// "Make variable effectively final" "true-preview"
// "Make 'x' effectively final by moving initializer to the 'if' statement" "true-preview"
class X {
void test(boolean a, boolean b, boolean c) {
int x = 0;

View File

@@ -1,4 +1,4 @@
// "Make variable effectively final" "true-preview"
// "Make 'x' effectively final by moving initializer to the 'if' statement" "true-preview"
class X {
void test(boolean a, boolean b, boolean c) {
int x = 0;

View File

@@ -1,4 +1,4 @@
// "Make variable effectively final" "false"
// "Make 'x' effectively final by moving initializer to the 'if' statement" "false"
class X {
void test(boolean a, int i) {
int x = i++;

View File

@@ -1,4 +1,4 @@
// "Make variable effectively final" "true-preview"
// "Make 'x' effectively final by moving initializer to the 'if' statement" "true-preview"
class X {
void test(boolean a) {
int x = 0;

View File

@@ -0,0 +1,15 @@
// "Make 'hasEmpty' effectively final using stream API" "true-preview"
import java.util.*;
class X {
void test(List<String> list) {
boolean hasEmpty = false;
for (String s : list) {
if (s.isEmpty()) {
hasEmpty = true;
break;
}
}
Runnable r = () -> System.out.println(<caret>hasEmpty);
}
}

View File

@@ -0,0 +1,16 @@
// "Make 'hasEmpty' effectively final using stream API" "false"
import java.util.*;
class X {
void test(List<String> list) {
boolean hasEmpty = false;
System.out.println(hasEmpty);
for (String s : list) {
if (s.isEmpty()) {
hasEmpty = true;
break;
}
}
Runnable r = () -> System.out.println(<caret>hasEmpty);
}
}

View File

@@ -0,0 +1,13 @@
// "Make 'x' effectively final using stream API" "true-preview"
import java.util.*;
class Test {
public static void main(String[] args) {
int x = 0;
for (String arg : args) {
if (!arg.isEmpty())
x++;
}
Runnable r = () -> System.out.println(<caret>x);
}
}

View File

@@ -0,0 +1,15 @@
// "Make 's' effectively final using stream API" "true-preview"
import java.util.*;
class Test {
public static void main(String[] args) {
String s = null;
for (String arg : args) {
if (arg.length() > 5) {
s = arg;
break;
}
}
Runnable r = () -> System.out.println(<caret>s);
}
}

View File

@@ -0,0 +1,14 @@
// "Make 'x' effectively final using stream API" "true-preview"
import java.util.*;
class Test {
public static void main(String[] args) {
int x = 0;
for (String arg : args) {
if (x < arg.length()) {
x = arg.length();
}
}
Runnable r = () -> System.out.println(<caret>x);
}
}

View File

@@ -0,0 +1,12 @@
// "Make 'x' effectively final using stream API" "true-preview"
import java.util.*;
class Test {
public static void main(String[] args) {
int x = 0;
for (String arg : args) {
x += arg.length();
}
Runnable r = () -> System.out.println(<caret>x);
}
}

View File

@@ -1,4 +1,4 @@
// "Make variable effectively final" "false"
// "Make 'x' effectively final by moving initializer to the 'if' statement" "false"
class X {
void test(boolean a, boolean b, boolean c, boolean d) {
int x = 0;

View File

@@ -1,4 +1,4 @@
// "Make variable effectively final" "true-preview"
// "Make 'x' effectively final by moving initializer to the 'if' statement" "true-preview"
class X {
void test(boolean a, int y) {
int x = y;

View File

@@ -1784,3 +1784,5 @@ adds.ext.library.preview=Adds library ''{0}'' to module ''{1}'' dependencies and
adds.module.dependencies.preview=Adds {0, choice, 1#module ''''{1}''''|2#one of {2}} to module ''{3}'' dependencies and imports unresolved classes
adds.library.preview=Adds {0, choice, 1#library ''''{1}''''|2#one of {2}} to module ''{3}'' dependencies and imports unresolved ''{4}''
notification.content.added.annotations=Added {0} {0, choice, 1#annotation|2#annotations}
intention.make.final.fixer.stream=Make ''{0}'' effectively final using stream API
intention.make.final.fixer.if=Make ''{0}'' effectively final by moving initializer to the ''if'' statement