mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-06 11:50:54 +07:00
[java-inspections] MakeVarEffectivelyFinalFix: stream API fixer
GitOrigin-RevId: e6f1c6e2d80af6332bae642391ca4b6113aa12ed
This commit is contained in:
committed by
intellij-monorepo-bot
parent
b95b63e818
commit
d9193529e2
@@ -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) {
|
||||
}
|
||||
}
|
||||
@@ -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">
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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++;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user