mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
for loop -> forEach call chain inspection
This commit is contained in:
+1
-1
@@ -675,7 +675,7 @@ public class HighlightControlFlowUtil {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean isEffectivelyFinal(PsiVariable variable, PsiElement scope, PsiJavaCodeReferenceElement context) {
|
||||
public static boolean isEffectivelyFinal(PsiVariable variable, PsiElement scope, PsiJavaCodeReferenceElement context) {
|
||||
boolean effectivelyFinal;
|
||||
if (variable instanceof PsiParameter) {
|
||||
effectivelyFinal = notAccessedForWriting(variable, new LocalSearchScope(((PsiParameter)variable).getDeclarationScope()));
|
||||
|
||||
+206
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
* Copyright 2000-2014 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.codeInspection;
|
||||
|
||||
import com.intellij.codeInsight.daemon.GroupNames;
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.pom.java.LanguageLevel;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.controlFlow.*;
|
||||
import com.intellij.psi.util.InheritanceUtil;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.util.containers.IntArrayList;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* User: anna
|
||||
*/
|
||||
public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTool {
|
||||
private static final Logger LOG = Logger.getInstance("#" + StreamApiMigrationInspection.class.getName());
|
||||
|
||||
@Nls
|
||||
@NotNull
|
||||
@Override
|
||||
public String getGroupDisplayName() {
|
||||
return GroupNames.LANGUAGE_LEVEL_SPECIFIC_GROUP_NAME;
|
||||
}
|
||||
|
||||
@Nls
|
||||
@NotNull
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return "foreach loop can be collapsed with stream api";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabledByDefault() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getShortName() {
|
||||
return "Convert2streamapi";
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
|
||||
return new JavaElementVisitor() {
|
||||
@Override
|
||||
public void visitForeachStatement(PsiForeachStatement statement) {
|
||||
super.visitForeachStatement(statement);
|
||||
if (PsiUtil.getLanguageLevel(statement).isAtLeast(LanguageLevel.JDK_1_8)) {
|
||||
final PsiExpression iteratedValue = statement.getIteratedValue();
|
||||
final PsiStatement body = statement.getBody();
|
||||
if (iteratedValue != null && body != null) {
|
||||
final PsiType iteratedValueType = iteratedValue.getType();
|
||||
if (InheritanceUtil.isInheritor(iteratedValueType, CommonClassNames.JAVA_LANG_ITERABLE)) {
|
||||
final PsiClass iteratorClass = PsiUtil.resolveClassInType(iteratedValueType);
|
||||
LOG.assertTrue(iteratorClass != null);
|
||||
try {
|
||||
final ControlFlow controlFlow = ControlFlowFactory.getInstance(holder.getProject())
|
||||
.getControlFlow(body, LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance());
|
||||
int startOffset = controlFlow.getStartOffset(body);
|
||||
int endOffset = controlFlow.getEndOffset(body);
|
||||
final Collection<PsiStatement> exitPoints = ControlFlowUtil
|
||||
.findExitPointsAndStatements(controlFlow, startOffset, endOffset, new IntArrayList(), PsiContinueStatement.class,
|
||||
PsiBreakStatement.class, PsiReturnStatement.class, PsiThrowStatement.class);
|
||||
if (exitPoints.isEmpty()) {
|
||||
|
||||
final boolean[] effectivelyFinal = new boolean[] {true};
|
||||
body.accept(new JavaRecursiveElementWalkingVisitor() {
|
||||
@Override
|
||||
public void visitElement(PsiElement element) {
|
||||
if (!effectivelyFinal[0]) return;
|
||||
super.visitElement(element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitReferenceExpression(PsiReferenceExpression expression) {
|
||||
if (!effectivelyFinal[0]) return;
|
||||
super.visitReferenceExpression(expression);
|
||||
final PsiElement resolve = expression.resolve();
|
||||
if (resolve instanceof PsiVariable && !(resolve instanceof PsiField)) {
|
||||
effectivelyFinal[0] = HighlightControlFlowUtil.isEffectivelyFinal((PsiVariable)resolve, body, expression);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (effectivelyFinal[0] && !isTrivial(body, statement.getIterationParameter(), iteratedValueType)) {
|
||||
holder.registerProblem(iteratedValue, "Can be replaced with foreach call",
|
||||
ProblemHighlightType.GENERIC_ERROR_OR_WARNING, new ReplaceWithForeachCallFix());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (AnalysisCanceledException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static boolean isTrivial(PsiStatement body, PsiParameter parameter, PsiType iteratedValueType) {
|
||||
final PsiIfStatement ifStatement = extractIfStatement(body);
|
||||
//stream
|
||||
if (ifStatement != null && ifStatement.getElseBranch() == null && ifStatement.getThenBranch() != null &&
|
||||
InheritanceUtil.isInheritor(iteratedValueType, CommonClassNames.JAVA_UTIL_COLLECTION)) {
|
||||
return false;
|
||||
}
|
||||
//method reference
|
||||
return LambdaCanBeMethodReferenceInspection.canBeMethodReferenceProblem(body instanceof PsiBlockStatement ? ((PsiBlockStatement)body).getCodeBlock() : body, new PsiParameter[] {parameter}, null) == null;
|
||||
}
|
||||
|
||||
private static class ReplaceWithForeachCallFix implements LocalQuickFix {
|
||||
@NotNull
|
||||
@Override
|
||||
public String getName() {
|
||||
return getFamilyName();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getFamilyName() {
|
||||
return "Replace with forEach";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
|
||||
final PsiForeachStatement foreachStatement = PsiTreeUtil.getParentOfType(descriptor.getPsiElement(), PsiForeachStatement.class);
|
||||
if (foreachStatement != null) {
|
||||
PsiStatement body = foreachStatement.getBody();
|
||||
final PsiExpression iteratedValue = foreachStatement.getIteratedValue();
|
||||
if (body != null && iteratedValue != null) {
|
||||
final PsiParameter parameter = foreachStatement.getIterationParameter();
|
||||
final PsiIfStatement ifStmt = extractIfStatement(body);
|
||||
|
||||
String foreEachText = body.getText();
|
||||
String iterated = iteratedValue.getText();
|
||||
if (ifStmt != null && ifStmt.getElseBranch() == null) {
|
||||
final PsiExpression condition = ifStmt.getCondition();
|
||||
if (condition != null) {
|
||||
final PsiStatement thenBranch = ifStmt.getThenBranch();
|
||||
if (thenBranch != null && InheritanceUtil.isInheritor(iteratedValue.getType(), CommonClassNames.JAVA_UTIL_COLLECTION)) {
|
||||
body = thenBranch;
|
||||
foreEachText = thenBranch.getText();
|
||||
iterated += ".stream().filter(" + parameter.getName() + " -> " + condition.getText() +")";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final PsiParameter[] parameters = {parameter};
|
||||
final PsiCallExpression expression = LambdaCanBeMethodReferenceInspection.canBeMethodReferenceProblem(body instanceof PsiBlockStatement ? ((PsiBlockStatement)body).getCodeBlock() : body, parameters, null);
|
||||
final String methodReferenceText = LambdaCanBeMethodReferenceInspection.createMethodReferenceText(expression, null, parameters);
|
||||
final String lambdaText = parameter.getName() + " -> " + foreEachText;
|
||||
final String codeBlock8 = methodReferenceText != null ? methodReferenceText : lambdaText;
|
||||
PsiExpressionStatement callStatement = (PsiExpressionStatement)JavaPsiFacade.getElementFactory(project).createStatementFromText(iterated + ".forEach(" + codeBlock8 + ");", foreachStatement);
|
||||
|
||||
callStatement = (PsiExpressionStatement)foreachStatement.replace(callStatement);
|
||||
final PsiExpressionList argumentList = ((PsiCallExpression)callStatement.getExpression()).getArgumentList();
|
||||
LOG.assertTrue(argumentList != null, callStatement.getText());
|
||||
final PsiExpression[] expressions = argumentList.getExpressions();
|
||||
LOG.assertTrue(expressions.length == 1);
|
||||
|
||||
if (expressions[0] instanceof PsiLambdaExpression && ((PsiLambdaExpression)expressions[0]).getFunctionalInterfaceType() == null ||
|
||||
expressions[0] instanceof PsiMethodReferenceExpression && ((PsiMethodReferenceExpression)expressions[0]).getFunctionalInterfaceType() == null) {
|
||||
callStatement = (PsiExpressionStatement)callStatement.replace(JavaPsiFacade.getElementFactory(project).createStatementFromText(iterated + ".forEach((" + parameter.getText() + ") -> " + foreEachText + ");", callStatement));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static PsiIfStatement extractIfStatement(PsiStatement body) {
|
||||
PsiIfStatement ifStmt = null;
|
||||
if (body instanceof PsiIfStatement) {
|
||||
ifStmt = (PsiIfStatement)body;
|
||||
} else if (body instanceof PsiBlockStatement) {
|
||||
final PsiStatement[] statements = ((PsiBlockStatement)body).getCodeBlock().getStatements();
|
||||
if (statements.length == 1 && statements[0] instanceof PsiIfStatement) {
|
||||
ifStmt = (PsiIfStatement)statements[0];
|
||||
}
|
||||
}
|
||||
return ifStmt;
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// "Replace with forEach" "true"
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
class Sample {
|
||||
List<String> foo = new ArrayList<>();
|
||||
{
|
||||
foo.forEach(this::bar);
|
||||
}
|
||||
|
||||
void bar(String s){}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// "Replace with forEach" "true"
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
class Sample {
|
||||
List<String> foo = new ArrayList<>();
|
||||
{
|
||||
foo.stream().filter(s -> s != null).forEach(System.out::println);
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// "Replace with forEach" "false"
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
class Sample {
|
||||
List<String> foo = new ArrayList<>();
|
||||
{
|
||||
for (String s : fo<caret>o) {
|
||||
if (s == null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// "Replace with forEach" "false"
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
class Sample {
|
||||
List<String> foo = new ArrayList<>();
|
||||
{
|
||||
for (String s : fo<caret>o) {
|
||||
if (s == null) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
// "Replace with forEach" "true"
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
class Sample {
|
||||
List<String> foo = new ArrayList<>();
|
||||
{
|
||||
for (String s : fo<caret>o) {
|
||||
bar(s)
|
||||
}
|
||||
}
|
||||
|
||||
void bar(String s){}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// "Replace with forEach" "false"
|
||||
class Sample {
|
||||
void foo(It it){
|
||||
for (String s : i<caret>t) {
|
||||
if (s == null) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class It implements Iterable<String> {}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
// "Replace with forEach" "true"
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
class Sample {
|
||||
List<String> foo = new ArrayList<>();
|
||||
{
|
||||
for (String s : fo<caret>o) {
|
||||
if (s != null) {
|
||||
System.out.println(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
// "Replace with forEach" "false"
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
class Sample {
|
||||
List<String> foo = new ArrayList<>();
|
||||
String foo(){
|
||||
boolean b = true;
|
||||
for (String s : f<caret>oo) {
|
||||
if (s == null) {
|
||||
b = false;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// "Replace with forEach" "false"
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
class Sample {
|
||||
List<String> foo = new ArrayList<>();
|
||||
String foo(){
|
||||
for (String s : fo<caret>o) {
|
||||
if (s == null) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2000-2014 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.codeInsight.daemon.quickFix;
|
||||
|
||||
import com.intellij.codeInspection.LocalInspectionTool;
|
||||
import com.intellij.codeInspection.StreamApiMigrationInspection;
|
||||
import com.intellij.openapi.projectRoots.Sdk;
|
||||
import com.intellij.testFramework.IdeaTestUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
||||
public class StreamApiMigrationInspectionTest extends LightQuickFixParameterizedTestCase {
|
||||
@NotNull
|
||||
@Override
|
||||
protected LocalInspectionTool[] configureLocalInspectionTools() {
|
||||
return new LocalInspectionTool[]{
|
||||
new StreamApiMigrationInspection(),
|
||||
};
|
||||
}
|
||||
|
||||
public void test() throws Exception { doAllTests(); }
|
||||
|
||||
@Override
|
||||
protected String getBasePath() {
|
||||
return "/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Sdk getProjectJDK() {
|
||||
return IdeaTestUtil.getMockJdk18();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
This inspection reports foreach loops which can be replaced with stream api calls.
|
||||
<p>
|
||||
Stream api is not available under Java 1.7 or earlier JVMs.
|
||||
</body>
|
||||
</html>
|
||||
@@ -626,6 +626,8 @@
|
||||
<localInspection language="JAVA" shortName="Convert2Lambda" displayName="Anonymous type can be replaced with lambda"
|
||||
groupName="Java language level migration aids" enabledByDefault="true" level="WARNING"
|
||||
implementationClass="com.intellij.codeInspection.AnonymousCanBeLambdaInspection" />
|
||||
<localInspection language="JAVA" shortName="Convert2streamapi" displayName="foreach loop can be collapsed with stream api"
|
||||
groupName="Java language level migration aids" enabledByDefault="true" level="WARNING" implementationClass="com.intellij.codeInspection.StreamApiMigrationInspection"/>
|
||||
<localInspection language="JAVA" shortName="Anonymous2MethodRef" displayName="Anonymous type can be replaced with method reference"
|
||||
groupName="Java language level migration aids" enabledByDefault="true" level="WARNING"
|
||||
implementationClass="com.intellij.codeInspection.AnonymousCanBeMethodReferenceInspection" />
|
||||
|
||||
Reference in New Issue
Block a user