From 3b11d2d28d420d849a62fbaf47389ef4aabc119d Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Fri, 1 Sep 2017 15:16:09 +0700 Subject: [PATCH 01/22] LiveVariablesAnalyzer#findLiveVars: clone bitset to avoid unwanted mutations of shared object --- .../intellij/codeInspection/dataFlow/LiveVariablesAnalyzer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/LiveVariablesAnalyzer.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/LiveVariablesAnalyzer.java index c63195e079ab..0c559889e31d 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/LiveVariablesAnalyzer.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/LiveVariablesAnalyzer.java @@ -148,7 +148,7 @@ public class LiveVariablesAnalyzer { set.or(liveVars); return set; } else { - result.put((FinishElementInstruction)instruction, liveVars); + result.put((FinishElementInstruction)instruction, (BitSet)liveVars.clone()); } } From 564afc8b7f840141b2483cc76bce2bd12de284c5 Mon Sep 17 00:00:00 2001 From: Dmitry Avdeev Date: Fri, 1 Sep 2017 12:39:46 +0300 Subject: [PATCH 02/22] IDEA-177037 {0} in title and description of actions in "Find Action" it seems the descriptors can't be seen anyway on welcome screen --- .../platform-resources-en/src/messages/ActionsBundle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/platform-resources-en/src/messages/ActionsBundle.properties b/platform/platform-resources-en/src/messages/ActionsBundle.properties index c9e209160ccc..b271d9454055 100644 --- a/platform/platform-resources-en/src/messages/ActionsBundle.properties +++ b/platform/platform-resources-en/src/messages/ActionsBundle.properties @@ -1368,7 +1368,7 @@ action.WelcomeScreen.DevelopPlugins.description=Get started developing plugins. action.WelcomeScreen.GetFromVcs.text=Check out from Version Control action.WelcomeScreen.GetFromVcs.description=You can check out an entire project from a Version Control System. Click the icon or link to select your VCS. action.WelcomeScreen.OpenProject.text=Open... -action.WelcomeScreen.OpenProject.description=Open file or project.
You can also open a project or edit a file by drag and drop onto Welcome screen. +action.WelcomeScreen.OpenProject.description=Open file or project. You can also open a project or edit a file by drag and drop onto Welcome screen. action.WelcomeScreen.CreateNewProject.text=Create New Project action.WelcomeScreen.CreateNewProject.description=Start the \"New Project\" Wizard that will guide you through the steps necessary for creating a new project. action.WelcomeScreen.ImportProject.text=Import Project From 0f2ac1a4856bb322c034595e6d5ecebf025bf85e Mon Sep 17 00:00:00 2001 From: Nicolay Mitropolsky Date: Fri, 1 Sep 2017 13:18:45 +0300 Subject: [PATCH 03/22] `PsiClassNamePatternCondition.stringPattern` renamed to `namePattern` --- .../patterns/PsiClassNamePatternCondition.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/java/java-psi-api/src/com/intellij/patterns/PsiClassNamePatternCondition.java b/java/java-psi-api/src/com/intellij/patterns/PsiClassNamePatternCondition.java index 2d448ed0a132..907f19a98754 100644 --- a/java/java-psi-api/src/com/intellij/patterns/PsiClassNamePatternCondition.java +++ b/java/java-psi-api/src/com/intellij/patterns/PsiClassNamePatternCondition.java @@ -22,7 +22,7 @@ import org.jetbrains.annotations.Nullable; public class PsiClassNamePatternCondition extends PatternCondition { - private final ElementPattern stringPattern; + private final ElementPattern namePattern; public PsiClassNamePatternCondition(ElementPattern pattern) { this("withQualifiedName", pattern); @@ -30,16 +30,16 @@ public class PsiClassNamePatternCondition extends PatternCondition { public PsiClassNamePatternCondition(@Nullable String debugMethodName, ElementPattern pattern) { super(debugMethodName); - stringPattern = pattern; + namePattern = pattern; } @Override public boolean accepts(@NotNull PsiClass aClass, ProcessingContext context) { - return stringPattern.accepts(aClass.getQualifiedName(), context); + return namePattern.accepts(aClass.getQualifiedName(), context); } @SuppressWarnings("unused") //Used in Kotlin - public ElementPattern getStringPattern() { - return stringPattern; + public ElementPattern getNamePattern() { + return namePattern; } } From 029b97749b189ea733daaee6429a85b81a02194c Mon Sep 17 00:00:00 2001 From: Roman Ivanov Date: Mon, 28 Aug 2017 09:20:42 +0700 Subject: [PATCH 04/22] extract simplify forEach inspection: IDEA-CR-24052 --- .../BaseStreamApiMigration.java | 30 +- .../streamMigration/CollectMigration.java | 32 +- .../streamMigration/CountMigration.java | 4 +- .../FindExtremumMigration.java | 12 +- .../streamMigration/FindFirstMigration.java | 4 +- .../streamMigration/ForEachMigration.java | 4 +- .../streamMigration/JoiningMigration.java | 50 ++- .../streamMigration/MatchMigration.java | 29 +- .../streamMigration/MigrateToStreamFix.java | 41 +- .../OperationReductionMigration.java | 4 +- .../SimplifyForEachInspection.java | 195 +++++++++ .../StreamApiMigrationInspection.java | 413 ++++++++++-------- .../streamMigration/SumMigration.java | 4 +- .../streamMigration/TerminalBlock.java | 35 +- .../streamMigration/ToArrayMigration.java | 6 +- .../afterBlockForEachToForEach.java | 10 + .../afterBlockForEachToForEachComments.java | 12 + .../afterCollectionForEachBlock.java | 10 + .../simplifyForEach/afterForEachBlock.java | 12 + .../afterForEachBlockFilter.java | 12 + .../afterForEachBlockFilterSortToArray.java | 10 + .../afterForEachBlockFilterSortToList.java | 12 + .../afterForEachBlockForEachOrdered.java | 10 + .../afterForEachBlockInlineInitializer.java | 10 + .../afterForEachExpressionLambda.java | 12 + .../afterForEachExpressionLambdaCount.java | 10 + .../afterForEachExpressionLambdaJoining.java | 11 + .../afterForEachExpressionLambdaToArray.java | 9 + .../afterForEachExpressionLambdaToMap.java | 11 + .../beforeBlockForEachToForEach.java | 12 + .../beforeBlockForEachToForEachComments.java | 12 + .../beforeCollectionForEachBlock.java | 14 + .../simplifyForEach/beforeForEachBlock.java | 11 + .../beforeForEachBlockFilter.java | 13 + .../beforeForEachBlockFilterSortToArray.java | 17 + .../beforeForEachBlockFilterSortToList.java | 16 + .../beforeForEachBlockForEachOrdered.java | 10 + .../beforeForEachBlockInlineInitializer.java | 10 + .../beforeForEachExpressionLambda.java | 11 + .../beforeForEachExpressionLambdaCount.java | 11 + .../beforeForEachExpressionLambdaJoining.java | 11 + .../beforeForEachExpressionLambdaToArray.java | 11 + .../beforeForEachExpressionLambdaToMap.java | 11 + .../SimplifyForEachInspectionTest.java | 45 ++ .../siyeh/ig/psiutils/ControlFlowUtils.java | 4 +- .../SimplifyForEach.html | 9 + resources/src/META-INF/IdeaPlugin.xml | 3 + 47 files changed, 958 insertions(+), 287 deletions(-) create mode 100644 java/java-impl/src/com/intellij/codeInspection/streamMigration/SimplifyForEachInspection.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterBlockForEachToForEach.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterBlockForEachToForEachComments.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterCollectionForEachBlock.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachBlock.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachBlockFilter.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachBlockFilterSortToArray.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachBlockFilterSortToList.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachBlockForEachOrdered.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachBlockInlineInitializer.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachExpressionLambda.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachExpressionLambdaCount.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachExpressionLambdaJoining.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachExpressionLambdaToArray.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachExpressionLambdaToMap.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeBlockForEachToForEach.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeBlockForEachToForEachComments.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeCollectionForEachBlock.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlock.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockFilter.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockFilterSortToArray.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockFilterSortToList.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockForEachOrdered.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockInlineInitializer.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambda.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaCount.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaJoining.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaToArray.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaToMap.java create mode 100644 java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/SimplifyForEachInspectionTest.java create mode 100644 resources-en/src/inspectionDescriptions/SimplifyForEach.html diff --git a/java/java-impl/src/com/intellij/codeInspection/streamMigration/BaseStreamApiMigration.java b/java/java-impl/src/com/intellij/codeInspection/streamMigration/BaseStreamApiMigration.java index 46cba24a9d6b..4f439f5c7bfa 100644 --- a/java/java-impl/src/com/intellij/codeInspection/streamMigration/BaseStreamApiMigration.java +++ b/java/java-impl/src/com/intellij/codeInspection/streamMigration/BaseStreamApiMigration.java @@ -28,7 +28,7 @@ import org.jetbrains.annotations.Nullable; * @author Tagir Valeev */ abstract class BaseStreamApiMigration { - private final boolean myShouldWarn; + private boolean myShouldWarn; private final String myReplacement; protected BaseStreamApiMigration(boolean shouldWarn, String replacement) { @@ -40,19 +40,23 @@ abstract class BaseStreamApiMigration { return myReplacement; } - abstract PsiElement migrate(@NotNull Project project, @NotNull PsiStatement body, @NotNull TerminalBlock tb); + abstract PsiElement migrate(@NotNull Project project, @NotNull PsiElement body, @NotNull TerminalBlock tb); public boolean isShouldWarn() { return myShouldWarn; } - static PsiElement replaceWithOperation(PsiLoopStatement loopStatement, + public void setShouldWarn(boolean shouldWarn) { + myShouldWarn = shouldWarn; + } + + static PsiElement replaceWithOperation(PsiStatement loopStatement, PsiVariable var, String streamText, PsiType expressionType, OperationReductionMigration.ReductionOperation reductionOperation) { PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(loopStatement.getProject()); - restoreComments(loopStatement, loopStatement.getBody()); + restoreComments(loopStatement, loopStatement instanceof PsiLoopStatement? ((PsiLoopStatement)loopStatement).getBody(): loopStatement); InitializerUsageStatus status = ControlFlowUtils.getInitializerUsageStatus(var, loopStatement); if (status != InitializerUsageStatus.UNKNOWN) { PsiExpression initializer = var.getInitializer(); @@ -67,7 +71,7 @@ abstract class BaseStreamApiMigration { loopStatement)); } - static PsiElement replaceInitializer(PsiLoopStatement loopStatement, + static PsiElement replaceInitializer(PsiStatement loopStatement, PsiVariable var, PsiExpression initializer, String replacement, @@ -90,11 +94,11 @@ abstract class BaseStreamApiMigration { @Nullable - static PsiElement replaceWithFindExtremum(@NotNull PsiLoopStatement loopStatement, + static PsiElement replaceWithFindExtremum(@NotNull PsiStatement loopStatement, @NotNull PsiVariable extremumHolder, @NotNull String streamText, @Nullable PsiVariable keyExtremum) { - restoreComments(loopStatement, loopStatement.getBody()); + restoreComments(loopStatement, loopStatement instanceof PsiLoopStatement? ((PsiLoopStatement)loopStatement).getBody(): loopStatement); if(keyExtremum != null) { keyExtremum.delete(); } @@ -102,14 +106,16 @@ abstract class BaseStreamApiMigration { return replaceInitializer(loopStatement, extremumHolder, extremumHolder.getInitializer(), streamText, status); } - static void restoreComments(PsiLoopStatement loopStatement, PsiStatement body) { - final PsiElement parent = loopStatement.getParent(); - for (PsiElement comment : PsiTreeUtil.findChildrenOfType(body, PsiComment.class)) { - parent.addBefore(comment, loopStatement); + static void restoreComments(PsiStatement statement, PsiElement body) { + if(statement instanceof PsiLoopStatement || statement instanceof PsiExpressionStatement) { + final PsiElement parent = statement.getParent(); + for (PsiElement comment : PsiTreeUtil.findChildrenOfType(body, PsiComment.class)) { + parent.addBefore(comment, statement); + } } } - static void removeLoop(@NotNull PsiLoopStatement statement) { + static void removeLoop(@NotNull PsiStatement statement) { PsiElement parent = statement.getParent(); if (parent instanceof PsiLabeledStatement) { parent.delete(); diff --git a/java/java-impl/src/com/intellij/codeInspection/streamMigration/CollectMigration.java b/java/java-impl/src/com/intellij/codeInspection/streamMigration/CollectMigration.java index 18a34d8263a7..72cc0d338d01 100644 --- a/java/java-impl/src/com/intellij/codeInspection/streamMigration/CollectMigration.java +++ b/java/java-impl/src/com/intellij/codeInspection/streamMigration/CollectMigration.java @@ -75,8 +75,8 @@ class CollectMigration extends BaseStreamApiMigration { } @Override - PsiElement migrate(@NotNull Project project, @NotNull PsiStatement body, @NotNull TerminalBlock tb) { - PsiLoopStatement loopStatement = tb.getMainLoop(); + PsiElement migrate(@NotNull Project project, @NotNull PsiElement body, @NotNull TerminalBlock tb) { + PsiStatement loopStatement = tb.getStreamSourceStatement(); PsiElementFactory factory = JavaPsiFacade.getElementFactory(project); CollectTerminal terminal = extractCollectTerminal(tb, null); if (terminal == null) return null; @@ -139,12 +139,12 @@ class CollectMigration extends BaseStreamApiMigration { CollectTerminal terminal = StreamEx.of(extractors).map(extractor -> extractor.apply(tb, call)).nonNull().findFirst().orElse(null); if (terminal != null) { if (terminal.getStatus() == ControlFlowUtils.InitializerUsageStatus.UNKNOWN) return null; - terminal = includePostStatements(terminal, tb.getMainLoop()); + terminal = includePostStatements(terminal, tb.getStreamSourceStatement()); } return terminal; } - static CollectTerminal includePostStatements(CollectTerminal terminal, PsiLoopStatement loop) { + static CollectTerminal includePostStatements(CollectTerminal terminal, PsiStatement loop) { List> wrappers = Arrays.asList(SortingTerminal::tryWrap, ToArrayTerminal::tryWrap, NewListTerminal::tryWrap); PsiElement nextStatement = loop; @@ -194,9 +194,9 @@ class CollectMigration extends BaseStreamApiMigration { abstract static class CollectTerminal { private final PsiLocalVariable myTargetVariable; private final InitializerUsageStatus myStatus; - final PsiLoopStatement myLoop; + final PsiStatement myLoop; - protected CollectTerminal(PsiLocalVariable variable, PsiLoopStatement loop, InitializerUsageStatus status) { + protected CollectTerminal(PsiLocalVariable variable, PsiStatement loop, InitializerUsageStatus status) { myTargetVariable = variable; myLoop = loop; myStatus = status; @@ -235,7 +235,7 @@ class CollectMigration extends BaseStreamApiMigration { AddingTerminal(@NotNull PsiLocalVariable target, PsiVariable element, PsiMethodCallExpression addCall, - PsiLoopStatement loop, + PsiStatement loop, InitializerUsageStatus status) { super(target, loop, hasLambdaCompatibleEmptyInitializer(target) ? status : ControlFlowUtils.InitializerUsageStatus.UNKNOWN); myTargetType = target.getType(); @@ -288,8 +288,8 @@ class CollectMigration extends BaseStreamApiMigration { PsiExpression count = tb.getCountExpression(); PsiLocalVariable variable = extractQualifierVariable(tb, call); if (variable != null) { - InitializerUsageStatus status = getInitializerUsageStatus(variable, tb.getMainLoop()); - AddingTerminal terminal = new AddingTerminal(variable, tb.getVariable(), call, tb.getMainLoop(), status); + InitializerUsageStatus status = getInitializerUsageStatus(variable, tb.getStreamSourceStatement()); + AddingTerminal terminal = new AddingTerminal(variable, tb.getVariable(), call, tb.getStreamSourceStatement(), status); if (count == null) return terminal; // like "list.add(x); if(list.size() >= limit) break;" if (!(count instanceof PsiMethodCallExpression)) return null; @@ -346,7 +346,7 @@ class CollectMigration extends BaseStreamApiMigration { AddingAllTerminal(PsiLocalVariable target, PsiVariable element, PsiMethodCallExpression addAllCall, - PsiLoopStatement loop, + PsiStatement loop, InitializerUsageStatus status) { super(target, element, null, loop, status); myAddAllCall = addAllCall; @@ -380,8 +380,8 @@ class CollectMigration extends BaseStreamApiMigration { if (collectionReference == null || tb.dependsOn(collectionReference)) return null; PsiLocalVariable target = tryCast(collectionReference.resolve(), PsiLocalVariable.class); if (target == null || StreamEx.of(args).skip(1).anyMatch(arg -> VariableAccessUtils.variableIsUsed(target, arg))) return null; - InitializerUsageStatus status = getInitializerUsageStatus(target, tb.getMainLoop()); - return new AddingAllTerminal(target, tb.getVariable(), call, tb.getMainLoop(), status); + InitializerUsageStatus status = getInitializerUsageStatus(target, tb.getStreamSourceStatement()); + return new AddingAllTerminal(target, tb.getVariable(), call, tb.getStreamSourceStatement(), status); } } @@ -447,7 +447,7 @@ class CollectMigration extends BaseStreamApiMigration { PsiType valueType = PsiUtil.substituteTypeParameter(mapType, CommonClassNames.JAVA_UTIL_MAP, 1, false); if (valueType == null) return null; AddingTerminal adding = new AddingTerminal(valueType, body, tb.getVariable(), call); - InitializerUsageStatus status = getInitializerUsageStatus(variable, tb.getMainLoop()); + InitializerUsageStatus status = getInitializerUsageStatus(variable, tb.getStreamSourceStatement()); return new GroupingTerminal(adding, variable, args[0], status); } } @@ -464,7 +464,7 @@ class CollectMigration extends BaseStreamApiMigration { ToMapTerminal(PsiMethodCallExpression call, PsiVariable elementVariable, PsiLocalVariable variable, - PsiLoopStatement loop, + PsiStatement loop, InitializerUsageStatus status) { super(variable, loop, status); myMapUpdateCall = call; @@ -517,8 +517,8 @@ class CollectMigration extends BaseStreamApiMigration { } PsiLocalVariable variable = extractQualifierVariable(tb, call); if (!hasLambdaCompatibleEmptyInitializer(variable)) return null; - InitializerUsageStatus status = getInitializerUsageStatus(variable, tb.getMainLoop()); - return new ToMapTerminal(call, tb.getVariable(), variable, tb.getMainLoop(), status); + InitializerUsageStatus status = getInitializerUsageStatus(variable, tb.getStreamSourceStatement()); + return new ToMapTerminal(call, tb.getVariable(), variable, tb.getStreamSourceStatement(), status); } } diff --git a/java/java-impl/src/com/intellij/codeInspection/streamMigration/CountMigration.java b/java/java-impl/src/com/intellij/codeInspection/streamMigration/CountMigration.java index 47317ec23267..1675d439e282 100644 --- a/java/java-impl/src/com/intellij/codeInspection/streamMigration/CountMigration.java +++ b/java/java-impl/src/com/intellij/codeInspection/streamMigration/CountMigration.java @@ -31,7 +31,7 @@ class CountMigration extends BaseStreamApiMigration { } @Override - PsiElement migrate(@NotNull Project project, @NotNull PsiStatement body, @NotNull TerminalBlock tb) { + PsiElement migrate(@NotNull Project project, @NotNull PsiElement body, @NotNull TerminalBlock tb) { PsiExpression expression = tb.getSingleExpression(PsiExpression.class); if (expression == null) { expression = tb.getCountExpression(); @@ -41,6 +41,6 @@ class CountMigration extends BaseStreamApiMigration { PsiElement element = ((PsiReferenceExpression)operand).resolve(); if (!(element instanceof PsiLocalVariable)) return null; PsiLocalVariable var = (PsiLocalVariable)element; - return replaceWithOperation(tb.getMainLoop(), var, tb.generate() + ".count()", PsiType.LONG, SUM_OPERATION); + return replaceWithOperation(tb.getStreamSourceStatement(), var, tb.generate() + ".count()", PsiType.LONG, SUM_OPERATION); } } diff --git a/java/java-impl/src/com/intellij/codeInspection/streamMigration/FindExtremumMigration.java b/java/java-impl/src/com/intellij/codeInspection/streamMigration/FindExtremumMigration.java index 253832c593b4..97eb53417491 100644 --- a/java/java-impl/src/com/intellij/codeInspection/streamMigration/FindExtremumMigration.java +++ b/java/java-impl/src/com/intellij/codeInspection/streamMigration/FindExtremumMigration.java @@ -46,7 +46,7 @@ class FindExtremumMigration extends BaseStreamApiMigration { } @Override - PsiElement migrate(@NotNull Project project, @NotNull PsiStatement body, @NotNull TerminalBlock tb) { + PsiElement migrate(@NotNull Project project, @NotNull PsiElement body, @NotNull TerminalBlock tb) { ExtremumTerminal terminal = extract(tb, null); if (terminal == null) return null; return terminal.replace(); @@ -187,7 +187,7 @@ class FindExtremumMigration extends BaseStreamApiMigration { static private boolean mayChangeBeforeLoop(@NotNull PsiVariable variable, @NotNull TerminalBlock terminalBlock) { ControlFlowUtils.InitializerUsageStatus status = - ControlFlowUtils.getInitializerUsageStatus(variable, terminalBlock.getMainLoop()); + ControlFlowUtils.getInitializerUsageStatus(variable, terminalBlock.getStreamSourceStatement()); return status.equals(ControlFlowUtils.InitializerUsageStatus.UNKNOWN); } @@ -286,7 +286,7 @@ class FindExtremumMigration extends BaseStreamApiMigration { if (method == null) return null; String inFilterOperation = myMax ? ">=" : "<="; - PsiLoopStatement loop = myTerminalBlock.getMainLoop(); + PsiStatement loop = myTerminalBlock.getStreamSourceStatement(); PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(loop.getProject()); String extremumInitializer = myExtremumKeyInitializer.getText(); PsiExpression condition = @@ -304,7 +304,7 @@ class FindExtremumMigration extends BaseStreamApiMigration { comparator = comparatorName; } String stream = blockWithFilter.generate() + "." + getOperation(myMax) + "(" + comparator + ").orElse(null)"; - return replaceWithFindExtremum(myTerminalBlock.getMainLoop(), myExtremum, stream, myExtremumKey); + return replaceWithFindExtremum(myTerminalBlock.getStreamSourceStatement(), myExtremum, stream, myExtremumKey); } @Override @@ -473,7 +473,7 @@ class FindExtremumMigration extends BaseStreamApiMigration { terminalBlock = blockWithMap; } String inFilterOperation = myMax ? ">=" : "<="; - PsiLoopStatement loop = terminalBlock.getMainLoop(); + PsiStatement loop = terminalBlock.getStreamSourceStatement(); PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(loop.getProject()); String extremumInitializer = myExtremumInitializer.getText(); Object nonFilterableInitialValue = getNonFilterableInitialValue(type, myMax); @@ -573,7 +573,7 @@ class FindExtremumMigration extends BaseStreamApiMigration { comparator = comparatorName; } String stream = myTerminalBlock.generate() + "." + getOperation(myMax) + "(" + comparator + ").orElse(null)"; - return replaceWithFindExtremum(myTerminalBlock.getMainLoop(), myExtremum, stream, null); + return replaceWithFindExtremum(myTerminalBlock.getStreamSourceStatement(), myExtremum, stream, null); } @Override diff --git a/java/java-impl/src/com/intellij/codeInspection/streamMigration/FindFirstMigration.java b/java/java-impl/src/com/intellij/codeInspection/streamMigration/FindFirstMigration.java index 735530f08d9b..fe0cec4c4070 100644 --- a/java/java-impl/src/com/intellij/codeInspection/streamMigration/FindFirstMigration.java +++ b/java/java-impl/src/com/intellij/codeInspection/streamMigration/FindFirstMigration.java @@ -34,10 +34,10 @@ class FindFirstMigration extends BaseStreamApiMigration { FindFirstMigration(boolean shouldWarn) {super(shouldWarn, "findFirst()");} @Override - PsiElement migrate(@NotNull Project project, @NotNull PsiStatement body, @NotNull TerminalBlock tb) { + PsiElement migrate(@NotNull Project project, @NotNull PsiElement body, @NotNull TerminalBlock tb) { PsiStatement statement = tb.getSingleStatement(); PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project); - PsiLoopStatement loopStatement = tb.getMainLoop(); + PsiStatement loopStatement = tb.getStreamSourceStatement(); if (statement instanceof PsiReturnStatement) { PsiReturnStatement returnStatement = (PsiReturnStatement)statement; PsiExpression value = returnStatement.getReturnValue(); diff --git a/java/java-impl/src/com/intellij/codeInspection/streamMigration/ForEachMigration.java b/java/java-impl/src/com/intellij/codeInspection/streamMigration/ForEachMigration.java index 46333df8eb63..0646a1556782 100644 --- a/java/java-impl/src/com/intellij/codeInspection/streamMigration/ForEachMigration.java +++ b/java/java-impl/src/com/intellij/codeInspection/streamMigration/ForEachMigration.java @@ -57,8 +57,8 @@ class ForEachMigration extends BaseStreamApiMigration { } @Override - PsiElement migrate(@NotNull Project project, @NotNull PsiStatement body, @NotNull TerminalBlock tb) { - PsiLoopStatement loopStatement = tb.getMainLoop(); + PsiElement migrate(@NotNull Project project, @NotNull PsiElement body, @NotNull TerminalBlock tb) { + PsiStatement loopStatement = tb.getStreamSourceStatement(); restoreComments(loopStatement, body); PsiElementFactory factory = JavaPsiFacade.getElementFactory(project); diff --git a/java/java-impl/src/com/intellij/codeInspection/streamMigration/JoiningMigration.java b/java/java-impl/src/com/intellij/codeInspection/streamMigration/JoiningMigration.java index 96d8ab64bc16..2adc34927586 100644 --- a/java/java-impl/src/com/intellij/codeInspection/streamMigration/JoiningMigration.java +++ b/java/java-impl/src/com/intellij/codeInspection/streamMigration/JoiningMigration.java @@ -26,6 +26,7 @@ import com.intellij.psi.search.searches.ReferencesSearch; import com.intellij.psi.util.InheritanceUtil; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtil; +import com.intellij.util.Processor; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.HashSet; import com.siyeh.ig.callMatcher.CallMatcher; @@ -37,6 +38,7 @@ import org.jetbrains.annotations.Nullable; import java.util.*; import java.util.function.BiFunction; +import java.util.function.Predicate; import java.util.stream.Collectors; import static com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil.isEffectivelyFinal; @@ -53,12 +55,12 @@ public class JoiningMigration extends BaseStreamApiMigration { } @Override - PsiElement migrate(@NotNull Project project, @NotNull PsiStatement body, @NotNull TerminalBlock tb) { + PsiElement migrate(@NotNull Project project, @NotNull PsiElement body, @NotNull TerminalBlock tb) { JoiningTerminal terminal = extractTerminal(tb, null); if(terminal == null) return null; TerminalBlock block = terminal.getTerminalBlock(); - PsiLoopStatement loopStatement = block.getMainLoop(); + PsiStatement loopStatement = block.getStreamSourceStatement(); String stream = terminal.generateStreamCode(); restoreComments(loopStatement, body); PsiLocalVariable builder = terminal.getBuilder(); @@ -171,10 +173,22 @@ public class JoiningMigration extends BaseStreamApiMigration { } } - private static boolean canBeMadeNonFinal(@NotNull PsiLocalVariable variable, @NotNull PsiLoopStatement loop) { - NavigatablePsiElement loopBound = PsiTreeUtil.getParentOfType(loop, PsiMember.class, PsiLambdaExpression.class); - return ReferencesSearch.search(variable) - .forEach(reference -> PsiTreeUtil.getParentOfType(reference.getElement(), PsiMember.class, PsiLambdaExpression.class) == loopBound); + private static boolean canBeMadeNonFinal(@NotNull PsiLocalVariable variable, @NotNull PsiStatement sourceStatement) { + NavigatablePsiElement loopBound = PsiTreeUtil.getParentOfType(sourceStatement, PsiMember.class, PsiLambdaExpression.class); + + Predicate referenceBoundPredicate; + if (sourceStatement instanceof PsiLoopStatement) { + referenceBoundPredicate = + (reference) -> PsiTreeUtil.getParentOfType(reference.getElement(), PsiMember.class, PsiLambdaExpression.class) == loopBound; + } + else { + referenceBoundPredicate = (reference) -> { + PsiLambdaExpression lambda = PsiTreeUtil.getParentOfType(reference.getElement(), PsiLambdaExpression.class); + return PsiTreeUtil.getParentOfType(lambda, PsiMember.class, PsiLambdaExpression.class) == loopBound; + }; + } + return ReferencesSearch.search(variable).forEach((Processor)reference -> referenceBoundPredicate.test(reference)) && + FinalUtils.canBeFinal(variable); } String generateTerminal() { @@ -197,6 +211,11 @@ public class JoiningMigration extends BaseStreamApiMigration { } String generateIntermediate() { + if (TypeUtils.isJavaLangString(myLoopVariable.getType()) && + myMainJoinParts.size() == 1 && + myMainJoinParts.get(0) instanceof PsiReferenceExpression) { + return ""; + } PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(myLoopVariable.getProject()); String joinTransformation = getExpressionText(myMainJoinParts); PsiExpression mapping = elementFactory.createExpressionFromText(joinTransformation, myLoopVariable); @@ -664,12 +683,13 @@ public class JoiningMigration extends BaseStreamApiMigration { if(declarations == null) return null; PsiMethodCallExpression beforeLoopAppend = getCallBeforeStatement(firstAppendSuccessor, targetBuilder, APPEND, declarations); - if(!canBeMadeNonFinal(targetBuilder, terminalBlock.getMainLoop())) return null; + + if (!canBeMadeNonFinal(targetBuilder, terminalBlock.getStreamSourceStatement())) return null; List refs = StreamEx.of(ReferencesSearch.search(targetBuilder).findAll()) .map(PsiReference::getElement) .remove(e -> PsiTreeUtil.isAncestor(targetBuilder, e, false) || - PsiTreeUtil.isAncestor(terminalBlock.getMainLoop(), e, false)) + PsiTreeUtil.isAncestor(terminalBlock.getStreamSourceStatement(), e, false)) .toList(); allowedReferencePlaces.add(afterLoopAppend); @@ -732,7 +752,7 @@ public class JoiningMigration extends BaseStreamApiMigration { if (mainJoinParts == null || mainJoinParts.isEmpty()) return null; PsiLocalVariable targetBuilder = extractStringBuilder(statements.get(0)); if (targetBuilder == null) return null; - PsiLoopStatement loop = terminalBlock.getMainLoop(); + PsiStatement loop = terminalBlock.getStreamSourceStatement(); PrefixSuffixContext context = PrefixSuffixContext.extractAndVerifyRefs(loop, loop, targetBuilder, terminalBlock, emptyList(), new HashSet<>(emptyList())); if (context == null) return null; @@ -776,7 +796,7 @@ public class JoiningMigration extends BaseStreamApiMigration { List mainJoinParts = extractJoinParts(withoutCondition); if (mainJoinParts == null) return null; - PsiLoopStatement loop = terminalBlock.getMainLoop(); + PsiStatement loop = terminalBlock.getStreamSourceStatement(); PrefixSuffixContext context = PrefixSuffixContext.extractAndVerifyRefs(loop, loop, targetBuilder, terminalBlock, emptyList(), new HashSet<>(emptyList())); if (context == null) return null; @@ -846,7 +866,7 @@ public class JoiningMigration extends BaseStreamApiMigration { PsiLocalVariable targetBuilder = extractStringBuilder(firstIterationStatements.get(0)); if (targetBuilder == null) return null; - PsiLoopStatement loop = terminalBlock.getMainLoop(); + PsiStatement loop = terminalBlock.getStreamSourceStatement(); PrefixSuffixContext context = PrefixSuffixContext.extractAndVerifyRefs(loop, loop, targetBuilder, terminalBlock, singletonList(boolVar), new HashSet<>(emptyList())); @@ -896,7 +916,7 @@ public class JoiningMigration extends BaseStreamApiMigration { List mainJoinParts = joinData.getMainJoinParts(); List delimiterJoinParts = joinData.getDelimiterJoinParts(); - PsiLoopStatement loop = terminalBlock.getMainLoop(); + PsiStatement loop = terminalBlock.getStreamSourceStatement(); PsiIfStatement ifStatement = tryCast(PsiTreeUtil.skipWhitespacesAndCommentsForward(loop), PsiIfStatement.class); if(ifStatement == null) return null; @@ -1019,7 +1039,7 @@ public class JoiningMigration extends BaseStreamApiMigration { PsiLocalVariable targetBuilder = extractStringBuilder(mainStatements.get(0)); if (targetBuilder == null) return null; - PsiLoopStatement loop = terminalBlock.getMainLoop(); + PsiStatement loop = terminalBlock.getStreamSourceStatement(); PrefixSuffixContext context = PrefixSuffixContext.extractAndVerifyRefs(loop, loop, targetBuilder, terminalBlock, singletonList(delimiterVar), new HashSet<>(emptyList())); @@ -1103,7 +1123,7 @@ public class JoiningMigration extends BaseStreamApiMigration { PsiLocalVariable targetBuilder = extractStringBuilder(firstIterationStatements.get(0)); if (targetBuilder == null) return null; - PsiLoopStatement loop = terminalBlock.getMainLoop(); + PsiStatement loop = terminalBlock.getStreamSourceStatement(); PrefixSuffixContext context = PrefixSuffixContext.extractAndVerifyRefs(loop, loop, targetBuilder, terminalBlock, emptyList(), new HashSet<>(emptyList())); if (context == null) return null; @@ -1180,7 +1200,7 @@ public class JoiningMigration extends BaseStreamApiMigration { JoinData joinData = JoinData.extractLeftDelimiter(joinParts); List delimiterJoinParts = joinData.getDelimiterJoinParts(); if (delimiterJoinParts.isEmpty()) return null; - PsiLoopStatement loop = terminalBlock.getMainLoop(); + PsiStatement loop = terminalBlock.getStreamSourceStatement(); PsiLocalVariable variable = tryCast(terminalBlock.getVariable(), PsiLocalVariable.class); if (variable == null) return null; PsiLocalVariable targetBuilder = extractStringBuilder(statements.get(0)); diff --git a/java/java-impl/src/com/intellij/codeInspection/streamMigration/MatchMigration.java b/java/java-impl/src/com/intellij/codeInspection/streamMigration/MatchMigration.java index f2c792bc37fd..01fb925c3630 100644 --- a/java/java-impl/src/com/intellij/codeInspection/streamMigration/MatchMigration.java +++ b/java/java-impl/src/com/intellij/codeInspection/streamMigration/MatchMigration.java @@ -36,29 +36,29 @@ class MatchMigration extends BaseStreamApiMigration { } @Override - PsiElement migrate(@NotNull Project project, @NotNull PsiStatement body, @NotNull TerminalBlock tb) { - PsiLoopStatement loopStatement = tb.getMainLoop(); + PsiElement migrate(@NotNull Project project, @NotNull PsiElement body, @NotNull TerminalBlock tb) { + PsiStatement sourceStatement = tb.getStreamSourceStatement(); PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project); if(tb.getSingleStatement() instanceof PsiReturnStatement) { PsiReturnStatement returnStatement = (PsiReturnStatement)tb.getSingleStatement(); PsiExpression value = returnStatement.getReturnValue(); if (ExpressionUtils.isLiteral(value, Boolean.TRUE) || ExpressionUtils.isLiteral(value, Boolean.FALSE)) { boolean foundResult = (boolean)((PsiLiteralExpression)value).getValue(); - PsiReturnStatement nextReturnStatement = StreamApiMigrationInspection.getNextReturnStatement(loopStatement); + PsiReturnStatement nextReturnStatement = StreamApiMigrationInspection.getNextReturnStatement(sourceStatement); if (nextReturnStatement != null) { PsiExpression returnValue = nextReturnStatement.getReturnValue(); if(returnValue == null) return null; String methodName = foundResult ? "anyMatch" : "noneMatch"; - String streamText = addTerminalOperation(methodName, loopStatement, tb); - restoreComments(loopStatement, body); - if (nextReturnStatement.getParent() == loopStatement.getParent()) { + String streamText = addTerminalOperation(methodName, sourceStatement, tb); + restoreComments(sourceStatement, body); + if (nextReturnStatement.getParent() == sourceStatement.getParent()) { if(!ExpressionUtils.isLiteral(returnValue, !foundResult)) { streamText+= (foundResult ? "||" : "&&") + ParenthesesUtils.getText(returnValue, ParenthesesUtils.AND_PRECEDENCE); } - removeLoop(loopStatement); + removeLoop(sourceStatement); return returnValue.replace(elementFactory.createExpressionFromText(streamText, nextReturnStatement)); } - PsiElement result = loopStatement.replace(elementFactory.createStatementFromText("return " + streamText + ";", loopStatement)); + PsiElement result = sourceStatement.replace(elementFactory.createStatementFromText("return " + streamText + ";", sourceStatement)); if(!isReachable(nextReturnStatement)) { nextReturnStatement.delete(); } @@ -67,11 +67,12 @@ class MatchMigration extends BaseStreamApiMigration { } } PsiStatement[] statements = tb.getStatements(); - if (!(statements.length == 1 || (statements.length == 2 && ControlFlowUtils.statementBreaksLoop(statements[1], loopStatement)))) { + if (!(statements.length == 1 || (sourceStatement instanceof PsiLoopStatement && statements.length == 2 && ControlFlowUtils.statementBreaksLoop(statements[1], + (PsiLoopStatement)sourceStatement)))) { return null; } - restoreComments(loopStatement, body); - String streamText = addTerminalOperation("anyMatch", loopStatement, tb); + restoreComments(sourceStatement, body); + String streamText = addTerminalOperation("anyMatch", sourceStatement, tb); PsiStatement statement = statements[0]; PsiAssignmentExpression assignment = ExpressionUtils.getAssignment(statement); if(assignment != null) { @@ -85,7 +86,7 @@ class MatchMigration extends BaseStreamApiMigration { // for(....) if(...) {flag = true; break;} PsiVariable var = (PsiVariable)maybeVar; PsiExpression initializer = var.getInitializer(); - InitializerUsageStatus status = ControlFlowUtils.getInitializerUsageStatus(var, loopStatement); + InitializerUsageStatus status = ControlFlowUtils.getInitializerUsageStatus(var, sourceStatement); if (initializer != null && status != ControlFlowUtils.InitializerUsageStatus.UNKNOWN) { String replacement; if (ExpressionUtils.isLiteral(initializer, Boolean.FALSE) && @@ -99,13 +100,13 @@ class MatchMigration extends BaseStreamApiMigration { else { replacement = streamText + "?" + rValue.getText() + ":" + initializer.getText(); } - return replaceInitializer(loopStatement, var, initializer, replacement, status); + return replaceInitializer(sourceStatement, var, initializer, replacement, status); } } } } String replacement = "if(" + streamText + "){" + statement.getText() + "}"; - return loopStatement.replace(elementFactory.createStatementFromText(replacement, loopStatement)); + return sourceStatement.replace(elementFactory.createStatementFromText(replacement, sourceStatement)); } private static String addTerminalOperation(String methodName, @NotNull PsiElement contextElement, @NotNull TerminalBlock tb) { diff --git a/java/java-impl/src/com/intellij/codeInspection/streamMigration/MigrateToStreamFix.java b/java/java-impl/src/com/intellij/codeInspection/streamMigration/MigrateToStreamFix.java index b7ab3486f0c4..14e275f9eae6 100644 --- a/java/java-impl/src/com/intellij/codeInspection/streamMigration/MigrateToStreamFix.java +++ b/java/java-impl/src/com/intellij/codeInspection/streamMigration/MigrateToStreamFix.java @@ -21,30 +21,33 @@ import com.intellij.codeInspection.ProblemDescriptor; import com.intellij.codeInspection.SimplifyStreamApiCallChainsInspection; import com.intellij.codeInspection.streamMigration.StreamApiMigrationInspection.StreamSource; import com.intellij.openapi.project.Project; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiLoopStatement; -import com.intellij.psi.PsiStatement; +import com.intellij.psi.*; import com.intellij.psi.codeStyle.CodeStyleManager; import com.intellij.psi.codeStyle.JavaCodeStyleManager; import com.intellij.psi.impl.PsiDiamondTypeUtil; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import static com.intellij.util.ObjectUtils.tryCast; /** * @author Tagir Valeev */ class MigrateToStreamFix implements LocalQuickFix { private BaseStreamApiMigration myMigration; + @Nullable private final String myCustomName; - protected MigrateToStreamFix(BaseStreamApiMigration migration) { + protected MigrateToStreamFix(BaseStreamApiMigration migration, @Nullable String customName) { myMigration = migration; + myCustomName = customName; } @Nls @NotNull @Override public String getName() { - return "Replace with "+myMigration.getReplacement(); + return myCustomName!= null? myCustomName: "Replace with "+myMigration.getReplacement(); } @SuppressWarnings("DialogTitleCapitalization") @@ -63,11 +66,29 @@ class MigrateToStreamFix implements LocalQuickFix { PsiStatement body = loopStatement.getBody(); if(body == null || source == null) return; TerminalBlock tb = TerminalBlock.from(source, body); - PsiElement result = myMigration.migrate(project, body, tb); - if(result != null) { - tb.operations().forEach(StreamApiMigrationInspection.Operation::cleanUp); - simplifyAndFormat(project, result); - } + migrate(project, body, tb); + } else if(element instanceof PsiExpressionStatement) { + PsiMethodCallExpression call = tryCast(((PsiExpressionStatement)element).getExpression(), PsiMethodCallExpression.class); + if(call == null) return; + + PsiLambdaExpression lambda = SimplifyForEachInspection.extractLambdaFromForEach(call); + if (lambda == null) return; + PsiElement lambdaBody = lambda.getBody(); + SimplifyForEachInspection.ExistingStreamSource + source = SimplifyForEachInspection.ExistingStreamSource.extractSource(call, lambda); + if(source == null) return; + TerminalBlock terminalBlock = SimplifyForEachInspection.extractTerminalBlock(lambdaBody, source); + if (terminalBlock == null) return; + + migrate(project, lambdaBody, terminalBlock); + } + } + + private void migrate(@NotNull Project project, PsiElement block, TerminalBlock tb) { + PsiElement result = myMigration.migrate(project, block, tb); + if(result != null) { + tb.operations().forEach(StreamApiMigrationInspection.Operation::cleanUp); + simplifyAndFormat(project, result); } } diff --git a/java/java-impl/src/com/intellij/codeInspection/streamMigration/OperationReductionMigration.java b/java/java-impl/src/com/intellij/codeInspection/streamMigration/OperationReductionMigration.java index 01034b4c4592..ced125ffe11e 100644 --- a/java/java-impl/src/com/intellij/codeInspection/streamMigration/OperationReductionMigration.java +++ b/java/java-impl/src/com/intellij/codeInspection/streamMigration/OperationReductionMigration.java @@ -40,7 +40,7 @@ public class OperationReductionMigration extends BaseStreamApiMigration { } @Override - PsiElement migrate(@NotNull Project project, @NotNull PsiStatement body, @NotNull TerminalBlock tb) { + PsiElement migrate(@NotNull Project project, @NotNull PsiElement body, @NotNull TerminalBlock tb) { PsiAssignmentExpression assignment = tb.getSingleExpression(PsiAssignmentExpression.class); if (assignment == null) return null; PsiVariable var = StreamApiMigrationInspection.extractAccumulator(assignment, myReductionOperation.getCompoundAssignmentOp()); @@ -71,7 +71,7 @@ public class OperationReductionMigration extends BaseStreamApiMigration { + String.format(Locale.ENGLISH, ".reduce(%s, (%s, %s) -> %s %s %s)", identity, leftOperand, rightOperand, leftOperand, myReductionOperation.getOperation(), rightOperand); - return replaceWithOperation(tb.getMainLoop(), var, stream, type, myReductionOperation); + return replaceWithOperation(tb.getStreamSourceStatement(), var, stream, type, myReductionOperation); } static class ReductionOperation { diff --git a/java/java-impl/src/com/intellij/codeInspection/streamMigration/SimplifyForEachInspection.java b/java/java-impl/src/com/intellij/codeInspection/streamMigration/SimplifyForEachInspection.java new file mode 100644 index 000000000000..acbea41d792d --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInspection/streamMigration/SimplifyForEachInspection.java @@ -0,0 +1,195 @@ +/* + * Copyright 2000-2017 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.streamMigration; + +import com.intellij.codeInsight.daemon.GroupNames; +import com.intellij.codeInspection.BaseJavaBatchLocalInspectionTool; +import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.lang.java.JavaLanguage; +import com.intellij.openapi.roots.FileIndexFacade; +import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.*; +import com.intellij.psi.impl.light.LightElement; +import com.intellij.psi.util.InheritanceUtil; +import com.intellij.psi.util.PsiUtil; +import com.siyeh.ig.callMatcher.CallMatcher; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import static com.intellij.util.ObjectUtils.tryCast; + +public class SimplifyForEachInspection extends BaseJavaBatchLocalInspectionTool { + private static final CallMatcher.Simple ITERABLE_FOREACH = + CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_ITERABLE, "forEach").parameterCount(1); + private static final CallMatcher.Simple STREAM_FOREACH_ORDERED = + CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_STREAM_BASE_STREAM, "forEachOrdered").parameterCount(1); + private static final CallMatcher STREAM_FOREACH = + CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_STREAM_BASE_STREAM, "forEach", "forEachOrdered").parameterCount(1); + private static final CallMatcher FOREACH = CallMatcher.anyOf( + STREAM_FOREACH, + ITERABLE_FOREACH + ); + + + @Nls + @NotNull + @Override + public String getGroupDisplayName() { + return GroupNames.LANGUAGE_LEVEL_SPECIFIC_GROUP_NAME; + } + + @Nls + @NotNull + @Override + public String getDisplayName() { + return "forEach call can be simplified"; + } + + + @NotNull + @Override + public String getShortName() { + return "SimplifyForEach"; + } + + @NotNull + @Override + public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { + PsiFile file = holder.getFile(); + VirtualFile virtualFile = file.getVirtualFile(); + if (!PsiUtil.isLanguageLevel8OrHigher(file) || virtualFile == null || + !FileIndexFacade.getInstance(holder.getProject()).isInSourceContent(virtualFile)) { + return PsiElementVisitor.EMPTY_VISITOR; + } + return new JavaElementVisitor() { + @Override + public void visitMethodCallExpression(PsiMethodCallExpression call) { + PsiLambdaExpression lambda = extractLambdaFromForEach(call); + if (lambda == null) return; + PsiElement lambdaBody = lambda.getBody(); + ExistingStreamSource + source = ExistingStreamSource.extractSource(call, lambda); + if (source == null) return; + TerminalBlock terminalBlock = extractTerminalBlock(lambdaBody, source); + if (terminalBlock == null) return; + + PsiStatement mainStatement = source.getMainStatement(); + BaseStreamApiMigration migration = + StreamApiMigrationInspection.findMigration(mainStatement, lambdaBody, terminalBlock, holder, true, true); + boolean opCountChanged = terminalBlock.getOperationCount() > 1; + boolean lastOpChanged = !(migration instanceof ForEachMigration); + if (opCountChanged || lastOpChanged) { + String customMessage; + if (opCountChanged && !lastOpChanged) { + customMessage = "Extract intermediate operations"; + if (STREAM_FOREACH_ORDERED.test(call)) { + migration = new ForEachMigration(migration.isShouldWarn(), "forEachOrdered"); + } + } + else { + customMessage = null; + } + if (migration != null) { + migration.setShouldWarn(true); + } + + StreamApiMigrationInspection + .offerMigration(mainStatement, terminalBlock, migration, m -> getRange(call).shiftRight(-call.getTextOffset()), customMessage, false, holder); + } + } + }; + } + + @NotNull + private static TextRange getRange(PsiMethodCallExpression call) { + PsiReferenceExpression methodExpression = call.getMethodExpression(); + return new TextRange(methodExpression.getTextOffset(), call.getArgumentList().getTextOffset()); + } + + @Nullable + static TerminalBlock extractTerminalBlock(@Nullable PsiElement lambdaBody, + @NotNull ExistingStreamSource source) { + if (lambdaBody instanceof PsiCodeBlock) { + return TerminalBlock.from(source, (PsiCodeBlock)lambdaBody); + } + if (lambdaBody instanceof PsiExpression) { + return TerminalBlock.fromStatements(source, new LightExpressionStatement((PsiExpression)lambdaBody)); + } + return null; + } + + @Nullable + static PsiLambdaExpression extractLambdaFromForEach(PsiMethodCallExpression call) { + PsiExpression qualifier = call.getMethodExpression().getQualifierExpression(); + if (qualifier == null || + !FOREACH.test(call) || + !InheritanceUtil.isInheritor(qualifier.getType(), CommonClassNames.JAVA_UTIL_COLLECTION)) return null; + PsiExpression arg = call.getArgumentList().getExpressions()[0]; + return tryCast(PsiUtil.skipParenthesizedExprDown(arg), PsiLambdaExpression.class); + } + + static class LightExpressionStatement extends LightElement implements PsiExpressionStatement { + @NotNull private final PsiExpression myExpression; + + protected LightExpressionStatement(@NotNull PsiExpression expression) { + super(expression.getManager(), JavaLanguage.INSTANCE); + myExpression = expression; + } + + @NotNull + @Override + public PsiExpression getExpression() { + return myExpression; + } + + @Override + public String toString() { + return myExpression.getText() + ";"; + } + } + + + static class ExistingStreamSource extends StreamApiMigrationInspection.StreamSource { + private final boolean myIsCollectionForEach; + + protected ExistingStreamSource(PsiStatement mainStatement, PsiVariable variable, PsiExpression expression, boolean isCollectionForEach) { + super(mainStatement, variable, expression); + myIsCollectionForEach = isCollectionForEach; + } + + @Override + String createReplacement() { + return myExpression.getText() + (myIsCollectionForEach? ".stream()" : ""); + } + + @Nullable + static ExistingStreamSource extractSource(PsiMethodCallExpression call, PsiLambdaExpression lambda) { + PsiParameter[] parameters = lambda.getParameterList().getParameters(); + if (parameters.length != 1) return null; + PsiParameter parameter = parameters[0]; + + boolean isCollectionForEach = ITERABLE_FOREACH.test(call); + + PsiExpression qualifier = call.getMethodExpression().getQualifierExpression(); + if (qualifier == null) return null; + PsiStatement parent = tryCast(call.getParent(), PsiExpressionStatement.class); + if (parent == null) return null; + return new ExistingStreamSource(parent, parameter, qualifier, isCollectionForEach); + } + } +} diff --git a/java/java-impl/src/com/intellij/codeInspection/streamMigration/StreamApiMigrationInspection.java b/java/java-impl/src/com/intellij/codeInspection/streamMigration/StreamApiMigrationInspection.java index 16d6b5a449e7..a6ee70f36c56 100644 --- a/java/java-impl/src/com/intellij/codeInspection/streamMigration/StreamApiMigrationInspection.java +++ b/java/java-impl/src/com/intellij/codeInspection/streamMigration/StreamApiMigrationInspection.java @@ -25,6 +25,7 @@ import com.intellij.codeInspection.LambdaCanBeMethodReferenceInspection; import com.intellij.codeInspection.ProblemHighlightType; import com.intellij.codeInspection.ProblemsHolder; import com.intellij.codeInspection.ui.MultipleCheckboxOptionsPanel; +import com.intellij.lang.java.JavaLanguage; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.FileIndexFacade; @@ -33,6 +34,7 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.profile.codeInspection.InspectionProjectProfileManager; import com.intellij.psi.*; import com.intellij.psi.controlFlow.*; +import com.intellij.psi.impl.light.LightElement; import com.intellij.psi.search.LocalSearchScope; import com.intellij.psi.search.searches.ReferencesSearch; import com.intellij.psi.tree.IElementType; @@ -41,6 +43,8 @@ import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtil; import com.intellij.psi.util.TypeConversionUtil; import com.intellij.util.ArrayUtil; +import com.intellij.util.containers.ContainerUtil; +import com.siyeh.ig.callMatcher.CallMatcher; import com.siyeh.ig.psiutils.*; import one.util.streamex.StreamEx; import org.jetbrains.annotations.Contract; @@ -52,6 +56,7 @@ import javax.swing.*; import java.util.Collection; import java.util.List; import java.util.Objects; +import java.util.function.Function; import static com.intellij.codeInspection.streamMigration.OperationReductionMigration.SUM_OPERATION; import static com.intellij.util.ObjectUtils.tryCast; @@ -60,8 +65,10 @@ import static com.siyeh.ig.psiutils.ControlFlowUtils.InitializerUsageStatus.UNKN public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTool { private static final Logger LOG = Logger.getInstance(StreamApiMigrationInspection.class); + public boolean REPLACE_TRIVIAL_FOREACH; public boolean SUGGEST_FOREACH; + private static final String SHORT_NAME = "Convert2streamapi"; @Nullable @Override @@ -94,7 +101,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo @NotNull @Override public String getShortName() { - return "Convert2streamapi"; + return SHORT_NAME; } @NotNull @@ -315,7 +322,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo } return variable != null && ExpressionUtils.isZero(variable.getInitializer()) && - ControlFlowUtils.getInitializerUsageStatus(variable, tb.getMainLoop()) != UNKNOWN; + ControlFlowUtils.getInitializerUsageStatus(variable, tb.getStreamSourceStatement()) != UNKNOWN; } private static boolean isTrivial(TerminalBlock tb) { @@ -437,203 +444,222 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo if (!ExceptionUtil.getThrownCheckedExceptions(body).isEmpty()) return; TerminalBlock tb = TerminalBlock.from(source, body); - BaseStreamApiMigration migration = findMigration(statement, body, tb); - if (migration != null && (myIsOnTheFly || migration.isShouldWarn())) { - MigrateToStreamFix[] fixes = {new MigrateToStreamFix(migration)}; - if (migration instanceof ForEachMigration && !(tb.getLastOperation() instanceof CollectionStream)) { //for .stream() - fixes = ArrayUtil.append(fixes, new MigrateToStreamFix(new ForEachMigration(migration.isShouldWarn(), "forEachOrdered"))); - } - ProblemHighlightType highlightType = - migration.isShouldWarn() ? ProblemHighlightType.GENERIC_ERROR_OR_WARNING : ProblemHighlightType.INFORMATION; - myHolder.registerProblem(statement, "Can be replaced with '" + migration.getReplacement() + "' call", - highlightType, getRange(migration.isShouldWarn(), statement).shiftRight(-statement.getTextOffset()), - fixes); - } + BaseStreamApiMigration migration = findMigration(statement, body, tb, myHolder, SUGGEST_FOREACH, REPLACE_TRIVIAL_FOREACH); + offerMigration(statement, tb, migration, (streamApiMigration) -> getRange(streamApiMigration.isShouldWarn(), statement, myIsOnTheFly) + .shiftRight(-statement.getTextOffset()), null, myIsOnTheFly, myHolder); } + } - @Nullable - private BaseStreamApiMigration findMigration(PsiLoopStatement loop, PsiStatement body, TerminalBlock tb) { - final ControlFlow controlFlow; - try { - controlFlow = ControlFlowFactory.getInstance(myHolder.getProject()) - .getControlFlow(body, LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance()); - } - catch (AnalysisCanceledException ignored) { - return null; - } - int startOffset = controlFlow.getStartOffset(body); - int endOffset = controlFlow.getEndOffset(body); - if (startOffset < 0 || endOffset < 0) return null; - PsiElement surrounder = PsiTreeUtil.getParentOfType(loop, PsiLambdaExpression.class, PsiClass.class); - final List nonFinalVariables = StreamEx.of(ControlFlowUtil.getUsedVariables(controlFlow, startOffset, endOffset)) - .remove(variable -> variable instanceof PsiField) - .remove(variable -> PsiTreeUtil.getParentOfType(variable, PsiLambdaExpression.class, PsiClass.class) != surrounder) - .remove(variable -> isVariableSuitableForStream(variable, loop, tb)).toList(); + static void offerMigration(PsiStatement statement, + TerminalBlock tb, + BaseStreamApiMigration migration, + Function rangeSupplier, + @Nullable String customMessage, + boolean isOnTheFly, + ProblemsHolder holder) { + if (migration != null && (isOnTheFly || migration.isShouldWarn())) { - if (isCountOperation(nonFinalVariables, tb)) { - return new CountMigration(true); - } - if (nonFinalVariables.size() == 0) { - CollectMigration.CollectTerminal terminal = CollectMigration.extractCollectTerminal(tb, nonFinalVariables); - if (terminal != null) { - boolean addAll = loop instanceof PsiForeachStatement && !tb.hasOperations() && isAddAllCall(tb); - // Don't suggest to convert the loop which can be trivially replaced via addAll: - // this is covered by UseBulkOperationInspection and ManualArrayToCollectionCopyInspection - if (addAll) return null; - boolean shouldWarn = REPLACE_TRIVIAL_FOREACH || - tb.hasOperations() || - tb.getLastOperation() instanceof BufferedReaderLines || - !terminal.isTrivial(); - return new CollectMigration(shouldWarn, terminal.getMethodName()); - } - } - if(JoiningMigration.extractTerminal(tb, nonFinalVariables) != null) { - return new JoiningMigration(true); - } - if (tb.getCountExpression() != null || tb.isEmpty()) return null; - if (nonFinalVariables.isEmpty() && extractArray(tb) != null) { - return new ToArrayMigration(true); - } - if (getAccumulatedVariable(tb, nonFinalVariables, SUM_OPERATION) != null) { - return new SumMigration(true); - } - FindExtremumMigration.ExtremumTerminal extremumTerminal = FindExtremumMigration.extract(tb, nonFinalVariables); - if(extremumTerminal != null) { - return new FindExtremumMigration(true, FindExtremumMigration.getOperation(extremumTerminal.isMax()) + "()"); - } - for (OperationReductionMigration.ReductionOperation reductionOperation : OperationReductionMigration.OPERATIONS) { - if (getAccumulatedVariable(tb, nonFinalVariables, reductionOperation) != null) { - return new OperationReductionMigration(true, reductionOperation); - } - } - Collection exitPoints = tb.findExitPoints(controlFlow); - if (exitPoints == null) return null; - boolean onlyNonLabeledContinue = StreamEx.of(exitPoints).allMatch(statement -> statement instanceof PsiContinueStatement && - ((PsiContinueStatement)statement).getLabelIdentifier() == null); - if (onlyNonLabeledContinue && nonFinalVariables.isEmpty()) { - boolean shouldWarn = SUGGEST_FOREACH && - (REPLACE_TRIVIAL_FOREACH || - tb.hasOperations() || - ForEachMigration.tryExtractMapExpression(tb) != null || - !isTrivial(tb)); - return new ForEachMigration(shouldWarn, "forEach"); - } - if (nonFinalVariables.isEmpty() && tb.getSingleStatement() instanceof PsiReturnStatement) { - return findMigrationForReturn(loop, tb); - } - // Source and intermediate ops should not refer to non-final variables - if (tb.intermediateAndSourceExpressions() - .flatCollection(expr -> PsiTreeUtil.collectElementsOfType(expr, PsiReferenceExpression.class)) - .map(PsiReferenceExpression::resolve).select(PsiVariable.class).anyMatch(nonFinalVariables::contains)) { - return null; - } - PsiStatement[] statements = tb.getStatements(); - if (statements.length == 2) { - PsiStatement breakStatement = statements[1]; - if (ControlFlowUtils.statementBreaksLoop(breakStatement, loop) && - exitPoints.size() == 1 && - exitPoints.contains(breakStatement)) { - return findMigrationForBreak(tb, nonFinalVariables, statements[0]); - } + MigrateToStreamFix[] fixes = {new MigrateToStreamFix(migration, customMessage)}; + if (migration instanceof ForEachMigration && !(tb.getLastOperation() instanceof CollectionStream)) { //for .stream() + fixes = ArrayUtil.append(fixes, new MigrateToStreamFix(new ForEachMigration(migration.isShouldWarn(), "forEachOrdered"), customMessage)); } + ProblemHighlightType highlightType = + migration.isShouldWarn() ? ProblemHighlightType.GENERIC_ERROR_OR_WARNING : ProblemHighlightType.INFORMATION; + String message = customMessage != null ? customMessage : "Can be replaced with '" + migration.getReplacement() + "' call"; + holder.registerProblem(statement, message, highlightType, rangeSupplier.apply(migration), fixes); + } + } + + + @Nullable + static BaseStreamApiMigration findMigration(PsiStatement loop, + PsiElement body, + TerminalBlock tb, + ProblemsHolder holder, + boolean suggestForeach, + boolean replaceTrivialForEach) { + final ControlFlow controlFlow; + try { + controlFlow = ControlFlowFactory.getInstance(holder.getProject()) + .getControlFlow(body, LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance()); + } + catch (AnalysisCanceledException ignored) { return null; } + int startOffset = controlFlow.getStartOffset(body); + int endOffset = controlFlow.getEndOffset(body); + if (startOffset < 0 || endOffset < 0) return null; + PsiElement surrounder = PsiTreeUtil.getParentOfType(loop, PsiLambdaExpression.class, PsiClass.class); + final List nonFinalVariables = StreamEx.of(ControlFlowUtil.getUsedVariables(controlFlow, startOffset, endOffset)) + .remove(variable -> variable instanceof PsiField) + .remove(variable -> PsiTreeUtil.getParentOfType(variable, PsiLambdaExpression.class, PsiClass.class) != surrounder) + .remove(variable -> isVariableSuitableForStream(variable, loop, tb)).toList(); - @Nullable - private BaseStreamApiMigration findMigrationForBreak(TerminalBlock tb, List nonFinalVariables, PsiStatement statement) { - boolean shouldWarn = REPLACE_TRIVIAL_FOREACH || tb.hasOperations(); - if (ReferencesSearch.search(tb.getVariable(), new LocalSearchScope(statement)).findFirst() == null) { - return new MatchMigration(shouldWarn, "anyMatch"); + if (isCountOperation(nonFinalVariables, tb)) { + return new CountMigration(true); + } + if (nonFinalVariables.size() == 0) { + CollectMigration.CollectTerminal terminal = CollectMigration.extractCollectTerminal(tb, nonFinalVariables); + if (terminal != null) { + boolean addAll = loop instanceof PsiForeachStatement && !tb.hasOperations() && isAddAllCall(tb); + // Don't suggest to convert the loop which can be trivially replaced via addAll: + // this is covered by UseBulkOperationInspection and ManualArrayToCollectionCopyInspection + if (addAll) return null; + boolean shouldWarn = replaceTrivialForEach || + tb.hasOperations() || + tb.getLastOperation() instanceof BufferedReaderLines || + !terminal.isTrivial(); + return new CollectMigration(shouldWarn, terminal.getMethodName()); } - if (nonFinalVariables.isEmpty() && statement instanceof PsiExpressionStatement) { - return new FindFirstMigration(shouldWarn); - } - if (nonFinalVariables.size() == 1) { - PsiAssignmentExpression assignment = ExpressionUtils.getAssignment(statement); - if (assignment == null) return null; - PsiReferenceExpression lValue = tryCast(assignment.getLExpression(), PsiReferenceExpression.class); - if (lValue == null) return null; - PsiVariable var = tryCast(lValue.resolve(), PsiVariable.class); - if (var == null || !nonFinalVariables.contains(var)) return null; - PsiExpression rValue = assignment.getRExpression(); - if (rValue == null || VariableAccessUtils.variableIsUsed(var, rValue)) return null; - if (tb.getVariable().getType() instanceof PsiPrimitiveType && !ExpressionUtils.isReferenceTo(rValue, tb.getVariable())) return null; - return new FindFirstMigration(shouldWarn); + } + if(JoiningMigration.extractTerminal(tb, nonFinalVariables) != null) { + return new JoiningMigration(true); + } + if (tb.getCountExpression() != null || tb.isEmpty()) return null; + if (nonFinalVariables.isEmpty() && extractArray(tb) != null) { + return new ToArrayMigration(true); + } + if (getAccumulatedVariable(tb, nonFinalVariables, SUM_OPERATION) != null) { + return new SumMigration(true); + } + FindExtremumMigration.ExtremumTerminal extremumTerminal = FindExtremumMigration.extract(tb, nonFinalVariables); + if(extremumTerminal != null) { + return new FindExtremumMigration(true, FindExtremumMigration.getOperation(extremumTerminal.isMax()) + "()"); + } + for (OperationReductionMigration.ReductionOperation reductionOperation : OperationReductionMigration.OPERATIONS) { + if (getAccumulatedVariable(tb, nonFinalVariables, reductionOperation) != null) { + return new OperationReductionMigration(true, reductionOperation); } + } + Collection exitPoints = tb.findExitPoints(controlFlow); + if (exitPoints == null) return null; + boolean onlyNonLabeledContinue = StreamEx.of(exitPoints).allMatch(statement -> statement instanceof PsiContinueStatement && ((PsiContinueStatement)statement).getLabelIdentifier() == null); + if (onlyNonLabeledContinue && nonFinalVariables.isEmpty()) { + boolean shouldWarn = suggestForeach && + (replaceTrivialForEach || + tb.hasOperations() || + ForEachMigration.tryExtractMapExpression(tb) != null || + !isTrivial(tb)); + return new ForEachMigration(shouldWarn, "forEach"); + } + if (nonFinalVariables.isEmpty() && tb.getSingleStatement() instanceof PsiReturnStatement) { + return findMigrationForReturn(loop, tb, replaceTrivialForEach); + } + // Source and intermediate ops should not refer to non-final variables + if (tb.intermediateAndSourceExpressions() + .flatCollection(expr -> PsiTreeUtil.collectElementsOfType(expr, PsiReferenceExpression.class)) + .map(PsiReferenceExpression::resolve).select(PsiVariable.class).anyMatch(nonFinalVariables::contains)) { return null; } - - @Nullable - private BaseStreamApiMigration findMigrationForReturn(PsiLoopStatement statement, TerminalBlock tb) { - boolean shouldWarn = REPLACE_TRIVIAL_FOREACH || tb.hasOperations(); - PsiReturnStatement returnStatement = (PsiReturnStatement)tb.getSingleStatement(); - PsiExpression value = returnStatement.getReturnValue(); - PsiReturnStatement nextReturnStatement = getNextReturnStatement(statement); - if (nextReturnStatement != null && - (ExpressionUtils.isLiteral(value, Boolean.TRUE) || ExpressionUtils.isLiteral(value, Boolean.FALSE))) { - boolean foundResult = (boolean)((PsiLiteralExpression)value).getValue(); - String methodName; - if (foundResult) { - methodName = "anyMatch"; - } - else { - methodName = "noneMatch"; - FilterOp lastFilter = tb.getLastOperation(FilterOp.class); - if (lastFilter != null && (lastFilter.isNegated() ^ BoolUtils.isNegation(lastFilter.getExpression()))) { - methodName = "allMatch"; - } - } - if (nextReturnStatement.getParent() == statement.getParent() || - ExpressionUtils.isLiteral(nextReturnStatement.getReturnValue(), !foundResult)) { - return new MatchMigration(shouldWarn, methodName); - } + PsiStatement[] statements = tb.getStatements(); + if (statements.length == 2) { + PsiStatement breakStatement = statements[1]; + if (loop instanceof PsiLoopStatement && ControlFlowUtils.statementBreaksLoop(breakStatement, (PsiLoopStatement)loop) && + exitPoints.size() == 1 && + exitPoints.contains(breakStatement)) { + return findMigrationForBreak(tb, nonFinalVariables, statements[0], replaceTrivialForEach); } - if (!VariableAccessUtils.variableIsUsed(tb.getVariable(), value)) { - if (!REPLACE_TRIVIAL_FOREACH && !tb.hasOperations() || - (tb.getLastOperation() instanceof FilterOp && tb.operations().count() == 2)) { - return null; - } - return new MatchMigration(shouldWarn, "anyMatch"); - } - if (nextReturnStatement != null && ExpressionUtils.isSimpleExpression(nextReturnStatement.getReturnValue()) - && (!(tb.getVariable().getType() instanceof PsiPrimitiveType) || ExpressionUtils.isReferenceTo(value, tb.getVariable()))) { - return new FindFirstMigration(shouldWarn); - } - return null; } + return null; + } - @NotNull - private TextRange getRange(boolean shouldWarn, PsiLoopStatement statement) { - boolean wholeStatement = - myIsOnTheFly && (!shouldWarn || InspectionProjectProfileManager.isInformationLevel(getShortName(), statement)); - if (statement instanceof PsiForeachStatement) { - PsiJavaToken rParenth = ((PsiForeachStatement)statement).getRParenth(); - if (wholeStatement && rParenth != null) { - return new TextRange(statement.getTextOffset(), rParenth.getTextOffset() + 1); - } - PsiExpression iteratedValue = ((PsiForeachStatement)statement).getIteratedValue(); - LOG.assertTrue(iteratedValue != null); - return iteratedValue.getTextRange(); - } - else if (statement instanceof PsiForStatement) { - PsiJavaToken rParenth = ((PsiForStatement)statement).getRParenth(); - if (wholeStatement && rParenth != null) { - return new TextRange(statement.getTextOffset(), rParenth.getTextOffset() + 1); - } - PsiStatement initialization = ((PsiForStatement)statement).getInitialization(); - LOG.assertTrue(initialization != null); - return initialization.getTextRange(); - } - else if (statement instanceof PsiWhileStatement) { - PsiJavaToken rParenth = ((PsiWhileStatement)statement).getRParenth(); - if (wholeStatement && rParenth != null) { - return new TextRange(statement.getTextOffset(), rParenth.getTextOffset() + 1); - } - return statement.getFirstChild().getTextRange(); + @Nullable + private static BaseStreamApiMigration findMigrationForBreak(TerminalBlock tb, + List nonFinalVariables, + PsiStatement statement, + boolean replaceTrivialForEach) { + boolean shouldWarn = replaceTrivialForEach || tb.hasOperations(); + if (ReferencesSearch.search(tb.getVariable(), new LocalSearchScope(statement)).findFirst() == null) { + return new MatchMigration(shouldWarn, "anyMatch"); + } + if (nonFinalVariables.isEmpty() && statement instanceof PsiExpressionStatement) { + return new FindFirstMigration(shouldWarn); + } + if (nonFinalVariables.size() == 1) { + PsiAssignmentExpression assignment = ExpressionUtils.getAssignment(statement); + if (assignment == null) return null; + PsiReferenceExpression lValue = tryCast(assignment.getLExpression(), PsiReferenceExpression.class); + if (lValue == null) return null; + PsiVariable var = tryCast(lValue.resolve(), PsiVariable.class); + if (var == null || !nonFinalVariables.contains(var)) return null; + PsiExpression rValue = assignment.getRExpression(); + if (rValue == null || VariableAccessUtils.variableIsUsed(var, rValue)) return null; + if (tb.getVariable().getType() instanceof PsiPrimitiveType && !ExpressionUtils.isReferenceTo(rValue, tb.getVariable())) return null; + return new FindFirstMigration(shouldWarn); + } + return null; + } + + @Nullable + private static BaseStreamApiMigration findMigrationForReturn(PsiStatement statement, TerminalBlock tb, boolean replaceTrivialForEach) { + boolean shouldWarn = replaceTrivialForEach || tb.hasOperations(); + PsiReturnStatement returnStatement = (PsiReturnStatement)tb.getSingleStatement(); + PsiExpression value = returnStatement.getReturnValue(); + PsiReturnStatement nextReturnStatement = getNextReturnStatement(statement); + if (nextReturnStatement != null && + (ExpressionUtils.isLiteral(value, Boolean.TRUE) || ExpressionUtils.isLiteral(value, Boolean.FALSE))) { + boolean foundResult = (boolean)((PsiLiteralExpression)value).getValue(); + String methodName; + if (foundResult) { + methodName = "anyMatch"; } else { - throw new IllegalStateException("Unexpected statement type: " + statement); + methodName = "noneMatch"; + FilterOp lastFilter = tb.getLastOperation(FilterOp.class); + if (lastFilter != null && (lastFilter.isNegated() ^ BoolUtils.isNegation(lastFilter.getExpression()))) { + methodName = "allMatch"; + } } + if (nextReturnStatement.getParent() == statement.getParent() || + ExpressionUtils.isLiteral(nextReturnStatement.getReturnValue(), !foundResult)) { + return new MatchMigration(shouldWarn, methodName); + } + } + if (!VariableAccessUtils.variableIsUsed(tb.getVariable(), value)) { + if (!replaceTrivialForEach && !tb.hasOperations() || + (tb.getLastOperation() instanceof FilterOp && tb.operations().count() == 2)) { + return null; + } + return new MatchMigration(shouldWarn, "anyMatch"); + } + if (nextReturnStatement != null && ExpressionUtils.isSimpleExpression(nextReturnStatement.getReturnValue()) + && (!(tb.getVariable().getType() instanceof PsiPrimitiveType) || ExpressionUtils.isReferenceTo(value, tb.getVariable()))) { + return new FindFirstMigration(shouldWarn); + } + return null; + } + + @NotNull + private static TextRange getRange(boolean shouldWarn, PsiStatement statement, boolean isOnTheFly) { + boolean wholeStatement = + isOnTheFly && (!shouldWarn || InspectionProjectProfileManager.isInformationLevel(SHORT_NAME, statement)); + if (statement instanceof PsiForeachStatement) { + PsiJavaToken rParenth = ((PsiForeachStatement)statement).getRParenth(); + if (wholeStatement && rParenth != null) { + return new TextRange(statement.getTextOffset(), rParenth.getTextOffset() + 1); + } + PsiExpression iteratedValue = ((PsiForeachStatement)statement).getIteratedValue(); + LOG.assertTrue(iteratedValue != null); + return iteratedValue.getTextRange(); + } + else if (statement instanceof PsiForStatement) { + PsiJavaToken rParenth = ((PsiForStatement)statement).getRParenth(); + if (wholeStatement && rParenth != null) { + return new TextRange(statement.getTextOffset(), rParenth.getTextOffset() + 1); + } + PsiStatement initialization = ((PsiForStatement)statement).getInitialization(); + LOG.assertTrue(initialization != null); + return initialization.getTextRange(); + } + else if (statement instanceof PsiWhileStatement) { + PsiJavaToken rParenth = ((PsiWhileStatement)statement).getRParenth(); + if (wholeStatement && rParenth != null) { + return new TextRange(statement.getTextOffset(), rParenth.getTextOffset() + 1); + } + return statement.getFirstChild().getTextRange(); + } + else { + throw new IllegalStateException("Unexpected statement type: " + statement); } } @@ -649,7 +675,9 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo PsiReferenceExpression arrayReference = tryCast(arrayAccess.getArrayExpression(), PsiReferenceExpression.class); if (arrayReference == null) return null; PsiLocalVariable arrayVariable = tryCast(arrayReference.resolve(), PsiLocalVariable.class); - if (arrayVariable == null || ControlFlowUtils.getInitializerUsageStatus(arrayVariable, tb.getMainLoop()) == UNKNOWN) return null; + if (arrayVariable == null || ControlFlowUtils.getInitializerUsageStatus(arrayVariable, tb.getStreamSourceStatement()) == UNKNOWN) { + return null; + } PsiNewExpression initializer = tryCast(arrayVariable.getInitializer(), PsiNewExpression.class); if (initializer == null) return null; PsiArrayType arrayType = tryCast(initializer.getType(), PsiArrayType.class); @@ -843,7 +871,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo } boolean breaksMe(PsiBreakStatement statement) { - return statement.findExitedStatement() == mySource.getLoop(); + return statement.findExitedStatement() == mySource.getMainStatement(); } } @@ -915,15 +943,15 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo } abstract static class StreamSource extends Operation { - private final PsiLoopStatement myLoop; + private final PsiStatement myMainStatement; - protected StreamSource(PsiLoopStatement loop, PsiVariable variable, PsiExpression expression) { + protected StreamSource(PsiStatement mainStatement, PsiVariable variable, PsiExpression expression) { super(expression, variable); - myLoop = loop; + myMainStatement = mainStatement; } - PsiLoopStatement getLoop() { - return myLoop; + PsiStatement getMainStatement() { + return myMainStatement; } @Contract("null -> null") @@ -1086,7 +1114,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo final PsiExpression myBound; final boolean myIncluding; - private CountingLoopSource(PsiLoopStatement loop, + private CountingLoopSource(PsiStatement loop, PsiVariable counter, PsiExpression initializer, PsiExpression bound, @@ -1109,11 +1137,11 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo } CountingLoopSource withBound(PsiExpression bound) { - return new CountingLoopSource(getLoop(), getVariable(), getExpression(), bound, myIncluding); + return new CountingLoopSource(getMainStatement(), getVariable(), getExpression(), bound, myIncluding); } CountingLoopSource withInitializer(PsiExpression expression) { - return new CountingLoopSource(getLoop(), getVariable(), expression, myBound, myIncluding); + return new CountingLoopSource(getMainStatement(), getVariable(), expression, myBound, myIncluding); } @Override @@ -1139,4 +1167,5 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo return new CountingLoopSource(forStatement, loop.getCounter(), loop.getInitializer(), loop.getBound(), loop.isIncluding()); } } + } diff --git a/java/java-impl/src/com/intellij/codeInspection/streamMigration/SumMigration.java b/java/java-impl/src/com/intellij/codeInspection/streamMigration/SumMigration.java index c33821ad90ec..51ddfb4c19cb 100644 --- a/java/java-impl/src/com/intellij/codeInspection/streamMigration/SumMigration.java +++ b/java/java-impl/src/com/intellij/codeInspection/streamMigration/SumMigration.java @@ -32,7 +32,7 @@ class SumMigration extends BaseStreamApiMigration { SumMigration(boolean shouldWarn) {super(shouldWarn, "sum()");} @Override - PsiElement migrate(@NotNull Project project, @NotNull PsiStatement body, @NotNull TerminalBlock tb) { + PsiElement migrate(@NotNull Project project, @NotNull PsiElement body, @NotNull TerminalBlock tb) { PsiAssignmentExpression assignment = tb.getSingleExpression(PsiAssignmentExpression.class); if (assignment == null) return null; PsiVariable var = StreamApiMigrationInspection.extractSumAccumulator(assignment); @@ -51,6 +51,6 @@ class SumMigration extends BaseStreamApiMigration { "(" + type.getCanonicalText() + ")" + ParenthesesUtils.getText(addend, ParenthesesUtils.MULTIPLICATIVE_PRECEDENCE), addend); } String stream = tb.add(new MapOp(addend, tb.getVariable(), type)).generate()+".sum()"; - return replaceWithOperation(tb.getMainLoop(), var, stream, type, SUM_OPERATION); + return replaceWithOperation(tb.getStreamSourceStatement(), var, stream, type, SUM_OPERATION); } } diff --git a/java/java-impl/src/com/intellij/codeInspection/streamMigration/TerminalBlock.java b/java/java-impl/src/com/intellij/codeInspection/streamMigration/TerminalBlock.java index b40f37f71032..1a142d7a552d 100644 --- a/java/java-impl/src/com/intellij/codeInspection/streamMigration/TerminalBlock.java +++ b/java/java-impl/src/com/intellij/codeInspection/streamMigration/TerminalBlock.java @@ -100,6 +100,10 @@ class TerminalBlock { return null; } + int getOperationCount() { + return myOperations.length; + } + /** * @return PsiMethodCallExpression if this TerminalBlock contains single method call, null otherwise */ @@ -191,12 +195,14 @@ class TerminalBlock { } if(myStatements.length >= 1) { PsiStatement first = myStatements[0]; - if(PsiUtil.isLanguageLevel9OrHigher(first.getContainingFile()) && first instanceof PsiIfStatement) { + if(PsiUtil.isLanguageLevel9OrHigher(myVariable.getContainingFile()) && first instanceof PsiIfStatement) { PsiIfStatement ifStatement = (PsiIfStatement)first; PsiExpression condition = ifStatement.getCondition(); if(ifStatement.getElseBranch() == null && condition != null) { PsiStatement thenStatement = ControlFlowUtils.stripBraces(ifStatement.getThenBranch()); - if(ControlFlowUtils.statementBreaksLoop(thenStatement, getMainLoop())) { + PsiStatement sourceStatement = getStreamSourceStatement(); + if( sourceStatement instanceof PsiLoopStatement && ControlFlowUtils.statementBreaksLoop(thenStatement, + (PsiLoopStatement)sourceStatement)) { TakeWhileOp op = new TakeWhileOp(condition, myVariable, true); PsiStatement[] leftOver = Arrays.copyOfRange(myStatements, 1, myStatements.length); return new TerminalBlock(this, op, myVariable, leftOver); @@ -252,7 +258,9 @@ class TerminalBlock { statements = Arrays.copyOfRange(myStatements, 0, count); tb = new TerminalBlock(myOperations, myVariable, Arrays.copyOfRange(myStatements, count, myStatements.length)).extractFilter(); } - if (tb == null || !ControlFlowUtils.statementBreaksLoop(tb.getSingleStatement(), getMainLoop())) return this; + PsiStatement sourceStatement = getStreamSourceStatement(); + if (tb == null || (sourceStatement instanceof PsiLoopStatement && !ControlFlowUtils.statementBreaksLoop(tb.getSingleStatement(), + (PsiLoopStatement)sourceStatement))) return this; FilterOp filter = tb.getLastOperation(FilterOp.class); if (filter == null) return this; PsiBinaryExpression binOp = tryCast(PsiUtil.skipParenthesizedExprDown(filter.getExpression()), PsiBinaryExpression.class); @@ -323,7 +331,7 @@ class TerminalBlock { PsiExpressionList argumentList = initializer.getArgumentList(); if (argumentList == null || argumentList.getExpressions().length != 0 || - ControlFlowUtils.getInitializerUsageStatus(var, getMainLoop()) == ControlFlowUtils.InitializerUsageStatus.UNKNOWN) { + ControlFlowUtils.getInitializerUsageStatus(var, getStreamSourceStatement()) == ControlFlowUtils.InitializerUsageStatus.UNKNOWN) { return null; } return var; @@ -428,8 +436,11 @@ class TerminalBlock { return StreamEx.ofReversed(myOperations); } - PsiLoopStatement getMainLoop() { - return ((StreamSource)myOperations[0]).getLoop(); + /** + * @return generally {@link PsiLoopStatement} - main loop + */ + PsiStatement getStreamSourceStatement() { + return ((StreamSource)myOperations[0]).getMainStatement(); } /** @@ -494,7 +505,17 @@ class TerminalBlock { @NotNull static TerminalBlock from(StreamSource source, @NotNull PsiStatement body) { - return new TerminalBlock(null, source, source.myVariable, body).extractOperations().tryPeelLimit(false).tryExtractDistinct(); + return fromStatements(source, body); + } + + @NotNull + static TerminalBlock fromStatements(StreamSource source, @NotNull PsiStatement... statements) { + return new TerminalBlock(null, source, source.myVariable, statements).extractOperations().tryPeelLimit(false).tryExtractDistinct(); + } + + @NotNull + static TerminalBlock from(StreamSource source, @NotNull PsiCodeBlock block) { + return fromStatements(source, block.getStatements()); } boolean dependsOn(PsiExpression qualifier) { diff --git a/java/java-impl/src/com/intellij/codeInspection/streamMigration/ToArrayMigration.java b/java/java-impl/src/com/intellij/codeInspection/streamMigration/ToArrayMigration.java index 5f590ae2b352..f57f939c3250 100644 --- a/java/java-impl/src/com/intellij/codeInspection/streamMigration/ToArrayMigration.java +++ b/java/java-impl/src/com/intellij/codeInspection/streamMigration/ToArrayMigration.java @@ -35,7 +35,7 @@ public class ToArrayMigration extends BaseStreamApiMigration { } @Override - PsiElement migrate(@NotNull Project project, @NotNull PsiStatement body, @NotNull TerminalBlock tb) { + PsiElement migrate(@NotNull Project project, @NotNull PsiElement body, @NotNull TerminalBlock tb) { PsiLocalVariable arrayVariable = StreamApiMigrationInspection.extractArray(tb); if(arrayVariable == null) return null; PsiAssignmentExpression assignment = tb.getSingleExpression(PsiAssignmentExpression.class); @@ -50,7 +50,7 @@ public class ToArrayMigration extends BaseStreamApiMigration { if(loop == null) return null; PsiArrayType arrayType = tryCast(initializer.getType(), PsiArrayType.class); if(arrayType == null) return null; - InitializerUsageStatus status = ControlFlowUtils.getInitializerUsageStatus(arrayVariable, tb.getMainLoop()); + InitializerUsageStatus status = ControlFlowUtils.getInitializerUsageStatus(arrayVariable, tb.getStreamSourceStatement()); if(status == ControlFlowUtils.InitializerUsageStatus.UNKNOWN) return null; PsiType componentType = arrayType.getComponentType(); String supplier; @@ -61,6 +61,6 @@ public class ToArrayMigration extends BaseStreamApiMigration { } MapOp mapping = new MapOp(rValue, tb.getVariable(), assignment.getType()); String replacementText = loop.withBound(dimension).createReplacement() + mapping.createReplacement() + ".toArray(" + supplier + ")"; - return replaceInitializer(tb.getMainLoop(), arrayVariable, initializer, replacementText, status); + return replaceInitializer(tb.getStreamSourceStatement(), arrayVariable, initializer, replacementText, status); } } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterBlockForEachToForEach.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterBlockForEachToForEach.java new file mode 100644 index 000000000000..9837c16d1158 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterBlockForEachToForEach.java @@ -0,0 +1,10 @@ +// "Extract intermediate operations" "true" + +import java.util.*; + +public class Main { + private void test() { + List other = new ArrayList<>(); + other.stream().filter(s -> s.length() > 2).forEach(System.out::println); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterBlockForEachToForEachComments.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterBlockForEachToForEachComments.java new file mode 100644 index 000000000000..e671a43190bd --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterBlockForEachToForEachComments.java @@ -0,0 +1,12 @@ +// "Extract intermediate operations" "true" + +import java.util.*; + +public class Main { + private void test() { + List other = new ArrayList<>(); + // c1 +//c2 + other.stream().filter(s -> s.length() > 2).forEach(System.out::println); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterCollectionForEachBlock.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterCollectionForEachBlock.java new file mode 100644 index 000000000000..49178ce0233b --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterCollectionForEachBlock.java @@ -0,0 +1,10 @@ +// "Replace with collect" "true" + +import java.util.*; +import java.util.stream.Collectors; + +public class Main { + private void test(List strs) { + List other = strs.stream().filter(s -> s.length() > 2).collect(Collectors.toList()); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachBlock.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachBlock.java new file mode 100644 index 000000000000..9b6ab2666df9 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachBlock.java @@ -0,0 +1,12 @@ +// "Replace with collect" "true" + +import java.util.*; +import java.util.stream.Collectors; + +public class Main { + private void test() { + List strs; + List other = new ArrayList<>(); + strs = other.stream().collect(Collectors.toList()); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachBlockFilter.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachBlockFilter.java new file mode 100644 index 000000000000..0e6bbb5d572f --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachBlockFilter.java @@ -0,0 +1,12 @@ +// "Replace with collect" "true" + +import java.util.*; +import java.util.stream.Collectors; + +public class Main { + private void test() { + List strs; + List other = new ArrayList<>(); + strs = other.stream().filter(s -> s.length() > 2).collect(Collectors.toList()); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachBlockFilterSortToArray.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachBlockFilterSortToArray.java new file mode 100644 index 000000000000..f917312e9d15 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachBlockFilterSortToArray.java @@ -0,0 +1,10 @@ +// "Replace with toArray" "true" + +import java.util.*; + +public class Main { + private void test() { + List other = new ArrayList<>(); + String[] arr = other.stream().filter(s -> s.length() > 2).sorted(String.CASE_INSENSITIVE_ORDER).toArray(); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachBlockFilterSortToList.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachBlockFilterSortToList.java new file mode 100644 index 000000000000..21135a76fa58 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachBlockFilterSortToList.java @@ -0,0 +1,12 @@ +// "Replace with collect" "true" + +import java.util.*; +import java.util.stream.Collectors; + +public class Main { + private void test() { + List strs; + List other = new ArrayList<>(); + strs = other.stream().filter(s -> s.length() > 2).sorted(String::compareTo).collect(Collectors.toList()); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachBlockForEachOrdered.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachBlockForEachOrdered.java new file mode 100644 index 000000000000..cb25201875b4 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachBlockForEachOrdered.java @@ -0,0 +1,10 @@ +// "Extract intermediate operations" "true" + +import java.util.*; + +public class Main { + private void test() { + List other = new ArrayList<>(); + other.stream().filter(s -> s.length() > 2).forEachOrdered(System.out::println); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachBlockInlineInitializer.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachBlockInlineInitializer.java new file mode 100644 index 000000000000..8166ed554f32 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachBlockInlineInitializer.java @@ -0,0 +1,10 @@ +// "Replace with collect" "true" + +import java.util.*; +import java.util.stream.Collectors; + +public class Main { + private void test(List other) { + List strs = other.stream().collect(Collectors.toList()); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachExpressionLambda.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachExpressionLambda.java new file mode 100644 index 000000000000..9b6ab2666df9 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachExpressionLambda.java @@ -0,0 +1,12 @@ +// "Replace with collect" "true" + +import java.util.*; +import java.util.stream.Collectors; + +public class Main { + private void test() { + List strs; + List other = new ArrayList<>(); + strs = other.stream().collect(Collectors.toList()); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachExpressionLambdaCount.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachExpressionLambdaCount.java new file mode 100644 index 000000000000..183a086dadb6 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachExpressionLambdaCount.java @@ -0,0 +1,10 @@ +// "Replace with count()" "true" + +import java.util.*; + +public class Main { + private void test() { + List strs = new ArrayList<>(); + int count = (int) strs.stream().count(); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachExpressionLambdaJoining.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachExpressionLambdaJoining.java new file mode 100644 index 000000000000..fb7bafb32432 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachExpressionLambdaJoining.java @@ -0,0 +1,11 @@ +// "Replace with collect" "true" + +import java.util.*; +import java.util.stream.Collectors; + +public class Main { + private void test() { + List strs = new ArrayList<>(); + String sb = strs.stream().collect(Collectors.joining()); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachExpressionLambdaToArray.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachExpressionLambdaToArray.java new file mode 100644 index 000000000000..4b0ef9561e0c --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachExpressionLambdaToArray.java @@ -0,0 +1,9 @@ +// "Replace with toArray" "true" + +import java.util.*; + +public class Main { + private void test(List strs) { + String[] arr = strs.toArray(); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachExpressionLambdaToMap.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachExpressionLambdaToMap.java new file mode 100644 index 000000000000..96d6090a83c2 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachExpressionLambdaToMap.java @@ -0,0 +1,11 @@ +// "Replace with collect" "true" + +import java.util.*; +import java.util.stream.Collectors; + +public class Main { + private void test() { + List other = new ArrayList<>(); + HashMap map = other.stream().collect(Collectors.toMap(String::length, s -> s, (a, b) -> a, HashMap::new)); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeBlockForEachToForEach.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeBlockForEachToForEach.java new file mode 100644 index 000000000000..1b61e425e2a9 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeBlockForEachToForEach.java @@ -0,0 +1,12 @@ +// "Extract intermediate operations" "true" + +import java.util.*; + +public class Main { + private void test() { + List other = new ArrayList<>(); + other.stream().forEach(s -> { + if(s.length() > 2) System.out.println(s); + }); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeBlockForEachToForEachComments.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeBlockForEachToForEachComments.java new file mode 100644 index 000000000000..95edff6e61ec --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeBlockForEachToForEachComments.java @@ -0,0 +1,12 @@ +// "Extract intermediate operations" "true" + +import java.util.*; + +public class Main { + private void test() { + List other = new ArrayList<>(); + other.stream().forEach(s -> { // c1 + if(s.length() > 2) System.out.println(s); //c2 + }); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeCollectionForEachBlock.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeCollectionForEachBlock.java new file mode 100644 index 000000000000..cc055f421440 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeCollectionForEachBlock.java @@ -0,0 +1,14 @@ +// "Replace with collect" "true" + +import java.util.*; + +public class Main { + private void test(List strs) { + List other = new ArrayList<>(); + strs.stream().forEach(s -> { + if(s.length() > 2) { + other.add(s); + } + }); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlock.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlock.java new file mode 100644 index 000000000000..fef5ce81635b --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlock.java @@ -0,0 +1,11 @@ +// "Replace with collect" "true" + +import java.util.*; + +public class Main { + private void test() { + List strs = new ArrayList<>(); + List other = new ArrayList<>(); + other.stream().forEach(s -> {strs.add(s)}); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockFilter.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockFilter.java new file mode 100644 index 000000000000..172148756f47 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockFilter.java @@ -0,0 +1,13 @@ +// "Replace with collect" "true" + +import java.util.*; + +public class Main { + private void test() { + List strs = new ArrayList<>(); + List other = new ArrayList<>(); + other.stream().forEach(s -> { + if(s.length() > 2) strs.add(s); + }); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockFilterSortToArray.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockFilterSortToArray.java new file mode 100644 index 000000000000..6ffe22fa0269 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockFilterSortToArray.java @@ -0,0 +1,17 @@ +// "Replace with toArray" "true" + +import java.util.*; + +public class Main { + private void test() { + List strs = new ArrayList<>(); + List other = new ArrayList<>(); + other.stream().forEach(s -> { + if(s.length() > 2) { + strs.add(s); + } + }); + String[] arr = strs.toArray(); + Arrays.sort(arr, String.CASE_INSENSITIVE_ORDER); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockFilterSortToList.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockFilterSortToList.java new file mode 100644 index 000000000000..c87f440e35ab --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockFilterSortToList.java @@ -0,0 +1,16 @@ +// "Replace with collect" "true" + +import java.util.*; + +public class Main { + private void test() { + List strs = new ArrayList<>(); + List other = new ArrayList<>(); + other.stream().forEach(s -> { + if(s.length() > 2) { + strs.add(s); + } + }); + strs.sort(String::compareTo); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockForEachOrdered.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockForEachOrdered.java new file mode 100644 index 000000000000..25fc5287df49 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockForEachOrdered.java @@ -0,0 +1,10 @@ +// "Extract intermediate operations" "true" + +import java.util.*; + +public class Main { + private void test() { + List other = new ArrayList<>(); + other.stream().forEachOrdered(s -> {if(s.length() > 2) System.out.println(s);}); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockInlineInitializer.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockInlineInitializer.java new file mode 100644 index 000000000000..4cc4647c973c --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockInlineInitializer.java @@ -0,0 +1,10 @@ +// "Replace with collect" "true" + +import java.util.*; + +public class Main { + private void test(List other) { + List strs = new ArrayList<>(); + other.stream().forEach(s -> {strs.add(s)}); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambda.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambda.java new file mode 100644 index 000000000000..c8f3e1e9e23f --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambda.java @@ -0,0 +1,11 @@ +// "Replace with collect" "true" + +import java.util.*; + +public class Main { + private void test() { + List strs = new ArrayList<>(); + List other = new ArrayList<>(); + other.stream().forEach(s -> strs.add(s)); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaCount.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaCount.java new file mode 100644 index 000000000000..fd7e758c9adc --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaCount.java @@ -0,0 +1,11 @@ +// "Replace with count()" "true" + +import java.util.*; + +public class Main { + private void test() { + List strs = new ArrayList<>(); + int count = 0; + strs.stream().forEach(x -> count++) + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaJoining.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaJoining.java new file mode 100644 index 000000000000..716186d2b26d --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaJoining.java @@ -0,0 +1,11 @@ +// "Replace with collect" "true" + +import java.util.*; + +public class Main { + private void test() { + List strs = new ArrayList<>(); + StringBuilder sb = new StringBuilder(); + strs.stream().forEach(x -> sb.append(x)); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaToArray.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaToArray.java new file mode 100644 index 000000000000..42949d7c66a6 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaToArray.java @@ -0,0 +1,11 @@ +// "Replace with toArray" "true" + +import java.util.*; + +public class Main { + private void test(List strs) { + List tmp = new ArrayList<>(); + strs.stream().forEach(x -> tmp.add(x)); + String[] arr = tmp.toArray(); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaToMap.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaToMap.java new file mode 100644 index 000000000000..65e43533a622 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaToMap.java @@ -0,0 +1,11 @@ +// "Replace with collect" "true" + +import java.util.*; + +public class Main { + private void test() { + List other = new ArrayList<>(); + HashMap map = new HashMap(); + other.stream().forEach(s -> map.putIfAbsent(s.length(), s)); + } +} diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/SimplifyForEachInspectionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/SimplifyForEachInspectionTest.java new file mode 100644 index 000000000000..22e001639f73 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/SimplifyForEachInspectionTest.java @@ -0,0 +1,45 @@ +/* + * Copyright 2000-2017 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.java.codeInsight.daemon.quickFix; + + +import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase; +import com.intellij.codeInspection.LocalInspectionTool; +import com.intellij.codeInspection.streamMigration.SimplifyForEachInspection; +import com.intellij.pom.java.LanguageLevel; +import org.jetbrains.annotations.NotNull; + +public class SimplifyForEachInspectionTest extends LightQuickFixParameterizedTestCase { + @NotNull + @Override + protected LocalInspectionTool[] configureLocalInspectionTools() { + return new LocalInspectionTool[]{new SimplifyForEachInspection()}; + } + + @Override + protected LanguageLevel getDefaultLanguageLevel() { + return LanguageLevel.JDK_1_8; + } + + public void test() { doAllTests(); } + + + + @Override + protected String getBasePath() { + return "/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach"; + } +} diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/ControlFlowUtils.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/ControlFlowUtils.java index 32dc6d6f2464..82dcc2646da8 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/ControlFlowUtils.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/ControlFlowUtils.java @@ -765,7 +765,7 @@ public class ControlFlowUtils { */ private static boolean isVariableReferencedBeforeLoopEntry(final ControlFlow flow, final int start, - final PsiLoopStatement loop, + final PsiStatement loop, final PsiVariable variable) { final int loopStart = flow.getStartOffset(loop); final int loopEnd = flow.getEndOffset(loop); @@ -812,7 +812,7 @@ public class ControlFlowUtils { * @return initializer usage status for variable */ @NotNull - public static InitializerUsageStatus getInitializerUsageStatus(PsiVariable var, PsiLoopStatement loop) { + public static InitializerUsageStatus getInitializerUsageStatus(PsiVariable var, PsiStatement loop) { if(!(var instanceof PsiLocalVariable) || var.getInitializer() == null) return UNKNOWN; if(isDeclarationJustBefore(var, loop)) return DECLARED_JUST_BEFORE; // Check that variable is declared in the same method or the same lambda expression diff --git a/resources-en/src/inspectionDescriptions/SimplifyForEach.html b/resources-en/src/inspectionDescriptions/SimplifyForEach.html new file mode 100644 index 000000000000..d5e8ea343b2e --- /dev/null +++ b/resources-en/src/inspectionDescriptions/SimplifyForEach.html @@ -0,0 +1,9 @@ + + +

+ This inspection reports forEach which can be replaced with more concise method or intermediate steps can be extracted. +

+Stream API is not available under Java 1.7 or earlier JVMs +New in 2017.3 + + \ No newline at end of file diff --git a/resources/src/META-INF/IdeaPlugin.xml b/resources/src/META-INF/IdeaPlugin.xml index 536a6366a913..5460c3d78cb7 100644 --- a/resources/src/META-INF/IdeaPlugin.xml +++ b/resources/src/META-INF/IdeaPlugin.xml @@ -805,6 +805,9 @@ + From f7e89c348e98c1ff3fca8b5e4d57cc9c50b47402 Mon Sep 17 00:00:00 2001 From: Roman Ivanov Date: Wed, 30 Aug 2017 12:44:10 +0700 Subject: [PATCH 05/22] fix foreach matching --- .../streamMigration/SimplifyForEachInspection.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/java/java-impl/src/com/intellij/codeInspection/streamMigration/SimplifyForEachInspection.java b/java/java-impl/src/com/intellij/codeInspection/streamMigration/SimplifyForEachInspection.java index acbea41d792d..69de83f9b806 100644 --- a/java/java-impl/src/com/intellij/codeInspection/streamMigration/SimplifyForEachInspection.java +++ b/java/java-impl/src/com/intellij/codeInspection/streamMigration/SimplifyForEachInspection.java @@ -40,10 +40,6 @@ public class SimplifyForEachInspection extends BaseJavaBatchLocalInspectionTool CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_STREAM_BASE_STREAM, "forEachOrdered").parameterCount(1); private static final CallMatcher STREAM_FOREACH = CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_STREAM_BASE_STREAM, "forEach", "forEachOrdered").parameterCount(1); - private static final CallMatcher FOREACH = CallMatcher.anyOf( - STREAM_FOREACH, - ITERABLE_FOREACH - ); @Nls @@ -136,13 +132,15 @@ public class SimplifyForEachInspection extends BaseJavaBatchLocalInspectionTool @Nullable static PsiLambdaExpression extractLambdaFromForEach(PsiMethodCallExpression call) { PsiExpression qualifier = call.getMethodExpression().getQualifierExpression(); - if (qualifier == null || - !FOREACH.test(call) || - !InheritanceUtil.isInheritor(qualifier.getType(), CommonClassNames.JAVA_UTIL_COLLECTION)) return null; + if (qualifier == null || !(STREAM_FOREACH.test(call) || isCollectionForEach(call, qualifier))) return null; PsiExpression arg = call.getArgumentList().getExpressions()[0]; return tryCast(PsiUtil.skipParenthesizedExprDown(arg), PsiLambdaExpression.class); } + private static boolean isCollectionForEach(PsiMethodCallExpression call, PsiExpression qualifier) { + return ITERABLE_FOREACH.test(call) && InheritanceUtil.isInheritor(qualifier.getType(), CommonClassNames.JAVA_UTIL_COLLECTION); + } + static class LightExpressionStatement extends LightElement implements PsiExpressionStatement { @NotNull private final PsiExpression myExpression; From 3fa1f3020ac29fa0f5022be0e20b2e2a66a672c7 Mon Sep 17 00:00:00 2001 From: Roman Ivanov Date: Fri, 1 Sep 2017 15:17:45 +0700 Subject: [PATCH 06/22] set information level by default for simplify forEach inspection --- resources/src/META-INF/IdeaPlugin.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/src/META-INF/IdeaPlugin.xml b/resources/src/META-INF/IdeaPlugin.xml index 5460c3d78cb7..c8dda0b73398 100644 --- a/resources/src/META-INF/IdeaPlugin.xml +++ b/resources/src/META-INF/IdeaPlugin.xml @@ -806,7 +806,7 @@ groupKey="group.names.language.level.specific.issues.and.migration.aids8" groupBundle="messages.InspectionsBundle" enabledByDefault="true" level="INFORMATION" implementationClass="com.intellij.codeInspection.streamMigration.StreamApiMigrationInspection"/> Date: Fri, 1 Sep 2017 17:02:56 +0700 Subject: [PATCH 07/22] fix tests --- .../quickFix/simplifyForEach/beforeBlockForEachToForEach.java | 2 +- .../simplifyForEach/beforeBlockForEachToForEachComments.java | 2 +- .../quickFix/simplifyForEach/beforeCollectionForEachBlock.java | 2 +- .../quickFix/simplifyForEach/beforeForEachBlock.java | 2 +- .../quickFix/simplifyForEach/beforeForEachBlockFilter.java | 2 +- .../simplifyForEach/beforeForEachBlockFilterSortToArray.java | 2 +- .../simplifyForEach/beforeForEachBlockFilterSortToList.java | 2 +- .../simplifyForEach/beforeForEachBlockForEachOrdered.java | 2 +- .../simplifyForEach/beforeForEachBlockInlineInitializer.java | 2 +- .../quickFix/simplifyForEach/beforeForEachExpressionLambda.java | 2 +- .../simplifyForEach/beforeForEachExpressionLambdaCount.java | 2 +- .../simplifyForEach/beforeForEachExpressionLambdaJoining.java | 2 +- .../simplifyForEach/beforeForEachExpressionLambdaToArray.java | 2 +- .../simplifyForEach/beforeForEachExpressionLambdaToMap.java | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeBlockForEachToForEach.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeBlockForEachToForEach.java index 1b61e425e2a9..35324823a498 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeBlockForEachToForEach.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeBlockForEachToForEach.java @@ -5,7 +5,7 @@ import java.util.*; public class Main { private void test() { List other = new ArrayList<>(); - other.stream().forEach(s -> { + other.stream().forEach(s -> { if(s.length() > 2) System.out.println(s); }); } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeBlockForEachToForEachComments.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeBlockForEachToForEachComments.java index 95edff6e61ec..06a6802c9f7e 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeBlockForEachToForEachComments.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeBlockForEachToForEachComments.java @@ -5,7 +5,7 @@ import java.util.*; public class Main { private void test() { List other = new ArrayList<>(); - other.stream().forEach(s -> { // c1 + other.stream().forEach(s -> { // c1 if(s.length() > 2) System.out.println(s); //c2 }); } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeCollectionForEachBlock.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeCollectionForEachBlock.java index cc055f421440..815e6da51526 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeCollectionForEachBlock.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeCollectionForEachBlock.java @@ -5,7 +5,7 @@ import java.util.*; public class Main { private void test(List strs) { List other = new ArrayList<>(); - strs.stream().forEach(s -> { + strs.stream().forEach(s -> { if(s.length() > 2) { other.add(s); } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlock.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlock.java index fef5ce81635b..76b0bdf08884 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlock.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlock.java @@ -6,6 +6,6 @@ public class Main { private void test() { List strs = new ArrayList<>(); List other = new ArrayList<>(); - other.stream().forEach(s -> {strs.add(s)}); + other.stream().forEach(s -> {strs.add(s)}); } } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockFilter.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockFilter.java index 172148756f47..0648335fa75a 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockFilter.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockFilter.java @@ -6,7 +6,7 @@ public class Main { private void test() { List strs = new ArrayList<>(); List other = new ArrayList<>(); - other.stream().forEach(s -> { + other.stream().forEach(s -> { if(s.length() > 2) strs.add(s); }); } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockFilterSortToArray.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockFilterSortToArray.java index 6ffe22fa0269..3fd3248017ff 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockFilterSortToArray.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockFilterSortToArray.java @@ -6,7 +6,7 @@ public class Main { private void test() { List strs = new ArrayList<>(); List other = new ArrayList<>(); - other.stream().forEach(s -> { + other.stream().forEach(s -> { if(s.length() > 2) { strs.add(s); } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockFilterSortToList.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockFilterSortToList.java index c87f440e35ab..c8747bd62645 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockFilterSortToList.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockFilterSortToList.java @@ -6,7 +6,7 @@ public class Main { private void test() { List strs = new ArrayList<>(); List other = new ArrayList<>(); - other.stream().forEach(s -> { + other.stream().forEach(s -> { if(s.length() > 2) { strs.add(s); } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockForEachOrdered.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockForEachOrdered.java index 25fc5287df49..e2e61eb31c60 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockForEachOrdered.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockForEachOrdered.java @@ -5,6 +5,6 @@ import java.util.*; public class Main { private void test() { List other = new ArrayList<>(); - other.stream().forEachOrdered(s -> {if(s.length() > 2) System.out.println(s);}); + other.stream().forEachOrdered(s -> {if(s.length() > 2) System.out.println(s);}); } } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockInlineInitializer.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockInlineInitializer.java index 4cc4647c973c..9af289dc1140 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockInlineInitializer.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockInlineInitializer.java @@ -5,6 +5,6 @@ import java.util.*; public class Main { private void test(List other) { List strs = new ArrayList<>(); - other.stream().forEach(s -> {strs.add(s)}); + other.stream().forEach(s -> {strs.add(s)}); } } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambda.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambda.java index c8f3e1e9e23f..1f22fdbb1ecb 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambda.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambda.java @@ -6,6 +6,6 @@ public class Main { private void test() { List strs = new ArrayList<>(); List other = new ArrayList<>(); - other.stream().forEach(s -> strs.add(s)); + other.stream().forEach(s -> strs.add(s)); } } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaCount.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaCount.java index fd7e758c9adc..bb02b157add4 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaCount.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaCount.java @@ -6,6 +6,6 @@ public class Main { private void test() { List strs = new ArrayList<>(); int count = 0; - strs.stream().forEach(x -> count++) + strs.stream().forEach(x -> count++) } } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaJoining.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaJoining.java index 716186d2b26d..aa5882ba3098 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaJoining.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaJoining.java @@ -6,6 +6,6 @@ public class Main { private void test() { List strs = new ArrayList<>(); StringBuilder sb = new StringBuilder(); - strs.stream().forEach(x -> sb.append(x)); + strs.stream().forEach(x -> sb.append(x)); } } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaToArray.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaToArray.java index 42949d7c66a6..29c33e867b6a 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaToArray.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaToArray.java @@ -5,7 +5,7 @@ import java.util.*; public class Main { private void test(List strs) { List tmp = new ArrayList<>(); - strs.stream().forEach(x -> tmp.add(x)); + strs.stream().forEach(x -> tmp.add(x)); String[] arr = tmp.toArray(); } } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaToMap.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaToMap.java index 65e43533a622..2f35ad133118 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaToMap.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaToMap.java @@ -6,6 +6,6 @@ public class Main { private void test() { List other = new ArrayList<>(); HashMap map = new HashMap(); - other.stream().forEach(s -> map.putIfAbsent(s.length(), s)); + other.stream().forEach(s -> map.putIfAbsent(s.length(), s)); } } From ff193191d39ad8aa18fad5678f2cb9dc25e07682 Mon Sep 17 00:00:00 2001 From: Ekaterina Tuzova Date: Tue, 22 Aug 2017 21:05:35 +0300 Subject: [PATCH 08/22] action to switch to scientific view in pycharm - part of PY-23595 - documentation docked on the right - documentation is auto updated from source - data view as toolwindow is shown only in scientific view - console is opened - restore scientific view after restart --- .../documentation/DockablePopupManager.java | 8 +- .../documentation/DocumentationManager.java | 2 +- python/src/META-INF/python-core-common.xml | 10 ++ .../python/actions/PySciProjectComponent.java | 151 ++++++++++++++++++ .../python/actions/PySciViewAction.java | 151 ++++++++++++++++++ .../python/console/PythonConsoleView.java | 13 ++ .../python/console/actions/ShowVarsAction.kt | 7 +- 7 files changed, 332 insertions(+), 10 deletions(-) create mode 100644 python/src/com/jetbrains/python/actions/PySciProjectComponent.java create mode 100644 python/src/com/jetbrains/python/actions/PySciViewAction.java diff --git a/platform/lang-impl/src/com/intellij/codeInsight/documentation/DockablePopupManager.java b/platform/lang-impl/src/com/intellij/codeInsight/documentation/DockablePopupManager.java index 5881498247f7..f0cdd35e6ffd 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/documentation/DockablePopupManager.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/documentation/DockablePopupManager.java @@ -47,7 +47,6 @@ import java.awt.*; public abstract class DockablePopupManager { protected ToolWindow myToolWindow; - private boolean myAutoUpdateDocumentation = PropertiesComponent.getInstance().isTrueValue(getAutoUpdateEnabledProperty()); private Runnable myAutoUpdateRequest; @NotNull protected final Project myProject; @@ -119,7 +118,7 @@ public abstract class DockablePopupManager { new UiNotifyConnector(component, new Activatable() { @Override public void showNotify() { - restartAutoUpdate(myAutoUpdateDocumentation); + restartAutoUpdate(PropertiesComponent.getInstance().getBoolean(getAutoUpdateEnabledProperty())); } @Override @@ -144,13 +143,12 @@ public abstract class DockablePopupManager { AllIcons.General.AutoscrollFromSource) { @Override public boolean isSelected(AnActionEvent e) { - return myAutoUpdateDocumentation; + return PropertiesComponent.getInstance().getBoolean(getAutoUpdateEnabledProperty()); } @Override public void setSelected(AnActionEvent e, boolean state) { PropertiesComponent.getInstance().setValue(getAutoUpdateEnabledProperty(), state); - myAutoUpdateDocumentation = state; restartAutoUpdate(state); } }; @@ -222,7 +220,7 @@ public abstract class DockablePopupManager { } - protected void restorePopupBehavior() { + public void restorePopupBehavior() { if (myToolWindow != null) { PropertiesComponent.getInstance().setValue(getShowInToolWindowProperty(), Boolean.FALSE.toString()); ToolWindowManagerEx toolWindowManagerEx = ToolWindowManagerEx.getInstanceEx(myProject); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/documentation/DocumentationManager.java b/platform/lang-impl/src/com/intellij/codeInsight/documentation/DocumentationManager.java index 4943255ec236..a00286221c87 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/documentation/DocumentationManager.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/documentation/DocumentationManager.java @@ -145,7 +145,7 @@ public class DocumentationManager extends DockablePopupManager + + + + @@ -867,6 +871,12 @@ + + + com.jetbrains.python.actions.PySciProjectComponent + + + com.jetbrains.python.testing.VFSTestFrameworkListener diff --git a/python/src/com/jetbrains/python/actions/PySciProjectComponent.java b/python/src/com/jetbrains/python/actions/PySciProjectComponent.java new file mode 100644 index 000000000000..38d8ff0b95c9 --- /dev/null +++ b/python/src/com/jetbrains/python/actions/PySciProjectComponent.java @@ -0,0 +1,151 @@ +/* + * Copyright 2000-2017 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.jetbrains.python.actions; + +import com.intellij.codeInsight.documentation.DocumentationManager; +import com.intellij.ide.DataManager; +import com.intellij.notification.NotificationDisplayType; +import com.intellij.notification.NotificationGroup; +import com.intellij.notification.NotificationListener; +import com.intellij.notification.NotificationType; +import com.intellij.openapi.actionSystem.ActionManager; +import com.intellij.openapi.actionSystem.ActionPlaces; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.components.AbstractProjectComponent; +import com.intellij.openapi.components.PersistentStateComponent; +import com.intellij.openapi.components.State; +import com.intellij.openapi.components.Storage; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleUtilCore; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.startup.StartupManager; +import com.intellij.psi.PsiDirectory; +import com.intellij.psi.PsiManager; +import com.jetbrains.python.packaging.PyPackage; +import com.jetbrains.python.packaging.PyPackageUtil; +import com.jetbrains.python.sdk.PythonSdkType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +import static com.jetbrains.python.actions.PySciViewAction.ACTION_ID; + +@State(name = "PySciProjectComponent", storages = @Storage("other.xml")) +public class PySciProjectComponent extends AbstractProjectComponent implements PersistentStateComponent { + private static final NotificationGroup BALLOON_NOTIFICATIONS = new NotificationGroup("Python Scientific View Advertiser", + NotificationDisplayType.STICKY_BALLOON, + false); + private State myState = new State(); + + protected PySciProjectComponent(Project project) { + super(project); + } + + public static PySciProjectComponent getInstance(Project project) { + return project.getComponent(PySciProjectComponent.class); + } + + public void useSciView(boolean useSciView) { + myState.PY_SCI_VIEW = useSciView; + } + + public boolean useSciView() { + return myState.PY_SCI_VIEW; + } + + @Override + public void projectOpened() { + if (myState.PY_SCI_VIEW) { + StartupManager.getInstance(myProject).runWhenProjectIsInitialized(() -> { + final PsiDirectory directory = PsiManager.getInstance(myProject).findDirectory(myProject.getBaseDir()); + if (directory != null) { + DocumentationManager.getInstance(myProject).showJavaDocInfo(directory, directory); + } + }); + } + else if (!myState.PY_SCI_VIEW_SUGGESTED) { + StartupManager.getInstance(myProject).runWhenProjectIsInitialized(() -> { + final PsiDirectory directory = PsiManager.getInstance(myProject).findDirectory(myProject.getBaseDir()); + if (directory != null) { + final Module module = ModuleUtilCore.findModuleForPsiElement(directory); + if (module != null) { + final Sdk sdk = PythonSdkType.findPythonSdk(module); + if (sdk != null) { + final List packages = PyPackageUtil.refreshAndGetPackagesModally(sdk); + final PyPackage numpy = PyPackageUtil.findPackage(packages, "numpy"); + + if (numpy != null) { + showInspectionAdvertisement(myProject); + } + } + } + } + }); + } + } + + private void showInspectionAdvertisement(@NotNull Project project) { + final String msg = "Your source code imports the 'numpy' package." + + "
Would you like to enable Scientific View?
" + + "Yes  No"; + showSingletonNotification(project, msg, NotificationType.INFORMATION, (notification, event) -> { + myState.PY_SCI_VIEW_SUGGESTED = true; + final boolean enabled = "#yes".equals(event.getDescription()); + if (enabled) { + final AnAction action = ActionManager.getInstance().getAction(ACTION_ID); + if (action instanceof PySciViewAction) { + final AnActionEvent anActionEvent = AnActionEvent.createFromDataContext( + ActionPlaces.UNKNOWN, null, DataManager.getInstance().getDataContextFromFocus().getResult()); + ((PySciViewAction)action).setSelected(anActionEvent, true); + } + } + }); + } + + private static void showSingletonNotification(@NotNull Project project, + @NotNull String htmlContent, + @NotNull NotificationType type, + @NotNull NotificationListener listener) { + BALLOON_NOTIFICATIONS.createNotification("Scientific View", htmlContent, type, (notification, event) -> { + try { + listener.hyperlinkUpdate(notification, event); + } + finally { + notification.expire(); + } + }).notify(project); + } + + @Nullable + @Override + public PySciProjectComponent.State getState() { + return myState; + } + + @Override + public void loadState(PySciProjectComponent.State state) { + myState.PY_SCI_VIEW = state.PY_SCI_VIEW; + myState.PY_SCI_VIEW_SUGGESTED = state.PY_SCI_VIEW_SUGGESTED; + } + + public static class State { + public boolean PY_SCI_VIEW = false; + public boolean PY_SCI_VIEW_SUGGESTED = false; + } +} diff --git a/python/src/com/jetbrains/python/actions/PySciViewAction.java b/python/src/com/jetbrains/python/actions/PySciViewAction.java new file mode 100644 index 000000000000..a885cbf09895 --- /dev/null +++ b/python/src/com/jetbrains/python/actions/PySciViewAction.java @@ -0,0 +1,151 @@ +/* + * Copyright 2000-2017 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.jetbrains.python.actions; + +import com.intellij.codeInsight.documentation.DocumentationManager; +import com.intellij.ide.util.PropertiesComponent; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.actionSystem.Presentation; +import com.intellij.openapi.actionSystem.ToggleAction; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.popup.JBPopup; +import com.intellij.openapi.wm.*; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiManager; +import com.jetbrains.python.console.PythonConsoleToolWindow; +import org.jetbrains.annotations.NotNull; + +import static com.jetbrains.python.debugger.containerview.PyDataView.DATA_VIEWER_ID; + +public class PySciViewAction extends ToggleAction implements DumbAware { + private static final String TEXT_SCI_VIEW = "Scientific Mode"; + + private boolean wasConsoleVisible = false; + private ToolWindowType myDocumentationType = ToolWindowType.FLOATING; + private boolean myDocumentationAutoHide = true; + + public static final String ACTION_ID = "PySciView"; + private ToolWindowType myDataViewType = ToolWindowType.FLOATING; + + public PySciViewAction() { + super(TEXT_SCI_VIEW); + } + + @Override + public boolean isSelected(AnActionEvent e) { + return e.getProject() != null && PySciProjectComponent.getInstance(e.getProject()).useSciView(); + } + + @Override + public void setSelected(AnActionEvent e, boolean state) { + final Project project = e.getProject(); + if (project == null) return; + PySciProjectComponent.getInstance(project).useSciView(state); + + final PsiElement element = getPsiElement(e, project); + + if (state) { + showConsoleToolwindow(project); + showDocumentationToolwindow(project, element); + showDataViewAsToolwindow(project); + } + else { + hideConsoleToolwindow(project); + restoreDocumentationPopup(project); + hideDataViewer(project); + } + } + + private void showConsoleToolwindow(@NotNull final Project project) { + final ToolWindow consoleToolWindow = PythonConsoleToolWindow.getInstance(project).getToolWindow(); + wasConsoleVisible = consoleToolWindow.isVisible(); + consoleToolWindow.show(null); + } + + private void hideConsoleToolwindow(@NotNull final Project project) { + if (!wasConsoleVisible) { + PythonConsoleToolWindow.getInstance(project).getToolWindow().hide(null); + } + } + + private void showDocumentationToolwindow(Project project, PsiElement element) { + final String showInToolWindowProperty = DocumentationManager.getInstance(project).getShowInToolWindowProperty(); + + PropertiesComponent.getInstance().setValue(showInToolWindowProperty, true); + PropertiesComponent.getInstance().setValue(DocumentationManager.getInstance(project).getAutoUpdateEnabledProperty(), true); + + if (element != null) { + DocumentationManager.getInstance(project).showJavaDocInfo(element, element); + } + final ToolWindow window = ToolWindowManager.getInstance(project).getToolWindow(ToolWindowId.DOCUMENTATION); + if (window != null) { + window.setAnchor(ToolWindowAnchor.RIGHT, null); + myDocumentationType = window.getType(); + myDocumentationAutoHide = window.isAutoHide(); + window.setType(ToolWindowType.DOCKED, null); + window.setAutoHide(false); + } + } + + private void restoreDocumentationPopup(Project project) { + PropertiesComponent.getInstance().setValue(DocumentationManager.getInstance(project).getAutoUpdateEnabledProperty(), false); + + final ToolWindow window = ToolWindowManager.getInstance(project).getToolWindow(ToolWindowId.DOCUMENTATION); + if (window != null) { + window.setType(myDocumentationType, null); + window.setAutoHide(myDocumentationAutoHide); + DocumentationManager.getInstance(project).restorePopupBehavior(); + final JBPopup hint = DocumentationManager.getInstance(project).getDocInfoHint(); + if (hint != null) { + hint.cancel(); + } + } + } + + private void showDataViewAsToolwindow(@NotNull final Project project) { + ToolWindow dataViewToolWindow = ToolWindowManager.getInstance(project).getToolWindow(DATA_VIEWER_ID); + myDataViewType = dataViewToolWindow.getType(); + dataViewToolWindow.setType(ToolWindowType.DOCKED, null); + dataViewToolWindow.setShowStripeButton(true); + } + + private void hideDataViewer(Project project) { + final ToolWindow dataViewer = ToolWindowManager.getInstance(project).getToolWindow(DATA_VIEWER_ID); + dataViewer.setType(myDataViewType, null); + dataViewer.setShowStripeButton(false); + } + + private static PsiElement getPsiElement(AnActionEvent e, Project project) { + PsiElement element = CommonDataKeys.PSI_ELEMENT.getData(e.getDataContext()); + if (element == null) { + element = CommonDataKeys.PSI_FILE.getData(e.getDataContext()); + } + if (element == null) { + element = PsiManager.getInstance(project).findDirectory(project.getBaseDir()); + } + return element; + } + + @Override + public void update(@NotNull AnActionEvent e) { + super.update(e); + final Project project = e.getProject(); + final Presentation presentation = e.getPresentation(); + presentation.setEnabledAndVisible(project != null); + } +} diff --git a/python/src/com/jetbrains/python/console/PythonConsoleView.java b/python/src/com/jetbrains/python/console/PythonConsoleView.java index c84deff5f118..10c6fe539502 100644 --- a/python/src/com/jetbrains/python/console/PythonConsoleView.java +++ b/python/src/com/jetbrains/python/console/PythonConsoleView.java @@ -95,6 +95,7 @@ public class PythonConsoleView extends LanguageConsoleImpl implements Observable private XStandaloneVariablesView mySplitView; private ActionCallback myInitialized = new ActionCallback(); + private boolean isShowVars = true; public PythonConsoleView(final Project project, final String title, final Sdk sdk) { super(project, title, PythonLanguage.getInstance()); @@ -115,6 +116,10 @@ public class PythonConsoleView extends LanguageConsoleImpl implements Observable public void setConsoleCommunication(final ConsoleCommunication communication) { getFile().putCopyableUserData(PydevConsoleRunner.CONSOLE_KEY, communication); + + if (isShowVars && communication instanceof PydevConsoleCommunication) { + showVariables((PydevConsoleCommunication)communication); + } } private PyConsoleStartFolding createConsoleFolding() { @@ -491,4 +496,12 @@ public class PythonConsoleView extends LanguageConsoleImpl implements Observable public void initialized() { myInitialized.setDone(); } + + public void setShowVars(boolean showVars) { + isShowVars = showVars; + } + + public boolean isShowVars() { + return isShowVars; + } } diff --git a/python/src/com/jetbrains/python/console/actions/ShowVarsAction.kt b/python/src/com/jetbrains/python/console/actions/ShowVarsAction.kt index 5485784d8110..a1bdc155040c 100644 --- a/python/src/com/jetbrains/python/console/actions/ShowVarsAction.kt +++ b/python/src/com/jetbrains/python/console/actions/ShowVarsAction.kt @@ -26,16 +26,15 @@ import com.jetbrains.python.console.PythonConsoleView * Created by Yuli Fiterman on 9/18/2016. */ class ShowVarsAction(private val consoleView: PythonConsoleView, private val consoleComm: PydevConsoleCommunication) : ToggleAction("Show Variables", "Shows active console variables", AllIcons.Debugger.Watches), DumbAware { - private var mySelected = false override fun isSelected(e: AnActionEvent): Boolean { - return mySelected + return consoleView.isShowVars } override fun setSelected(e: AnActionEvent, state: Boolean) { - mySelected = state + consoleView.isShowVars = state - if (mySelected) { + if (state) { consoleView.showVariables(consoleComm) } else { From df4dd29135789842e565c9b34526f3ba1a35de4f Mon Sep 17 00:00:00 2001 From: Ekaterina Tuzova Date: Tue, 22 Aug 2017 21:10:26 +0300 Subject: [PATCH 09/22] proper color for documentation background --- .../python/actions/PySciProjectComponent.java | 7 +++++++ .../jetbrains/python/actions/PySciViewAction.java | 13 +++++++++++++ 2 files changed, 20 insertions(+) diff --git a/python/src/com/jetbrains/python/actions/PySciProjectComponent.java b/python/src/com/jetbrains/python/actions/PySciProjectComponent.java index 38d8ff0b95c9..206e892da398 100644 --- a/python/src/com/jetbrains/python/actions/PySciProjectComponent.java +++ b/python/src/com/jetbrains/python/actions/PySciProjectComponent.java @@ -29,6 +29,8 @@ import com.intellij.openapi.components.AbstractProjectComponent; import com.intellij.openapi.components.PersistentStateComponent; import com.intellij.openapi.components.State; import com.intellij.openapi.components.Storage; +import com.intellij.openapi.editor.colors.EditorColorsManager; +import com.intellij.openapi.editor.colors.EditorColorsScheme; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleUtilCore; import com.intellij.openapi.project.Project; @@ -36,6 +38,7 @@ import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.startup.StartupManager; import com.intellij.psi.PsiDirectory; import com.intellij.psi.PsiManager; +import com.intellij.util.ui.UIUtil; import com.jetbrains.python.packaging.PyPackage; import com.jetbrains.python.packaging.PyPackageUtil; import com.jetbrains.python.sdk.PythonSdkType; @@ -44,6 +47,7 @@ import org.jetbrains.annotations.Nullable; import java.util.List; +import static com.intellij.codeInsight.documentation.DocumentationComponent.COLOR_KEY; import static com.jetbrains.python.actions.PySciViewAction.ACTION_ID; @State(name = "PySciProjectComponent", storages = @Storage("other.xml")) @@ -73,6 +77,9 @@ public class PySciProjectComponent extends AbstractProjectComponent implements P public void projectOpened() { if (myState.PY_SCI_VIEW) { StartupManager.getInstance(myProject).runWhenProjectIsInitialized(() -> { + EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme(); + scheme.setColor(COLOR_KEY, UIUtil.getEditorPaneBackground()); + final PsiDirectory directory = PsiManager.getInstance(myProject).findDirectory(myProject.getBaseDir()); if (directory != null) { DocumentationManager.getInstance(myProject).showJavaDocInfo(directory, directory); diff --git a/python/src/com/jetbrains/python/actions/PySciViewAction.java b/python/src/com/jetbrains/python/actions/PySciViewAction.java index a885cbf09895..0fc149ae3727 100644 --- a/python/src/com/jetbrains/python/actions/PySciViewAction.java +++ b/python/src/com/jetbrains/python/actions/PySciViewAction.java @@ -21,15 +21,21 @@ import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.actionSystem.Presentation; import com.intellij.openapi.actionSystem.ToggleAction; +import com.intellij.openapi.editor.colors.EditorColorsManager; +import com.intellij.openapi.editor.colors.EditorColorsScheme; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.popup.JBPopup; import com.intellij.openapi.wm.*; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiManager; +import com.intellij.util.ui.UIUtil; import com.jetbrains.python.console.PythonConsoleToolWindow; import org.jetbrains.annotations.NotNull; +import java.awt.*; + +import static com.intellij.codeInsight.documentation.DocumentationComponent.COLOR_KEY; import static com.jetbrains.python.debugger.containerview.PyDataView.DATA_VIEWER_ID; public class PySciViewAction extends ToggleAction implements DumbAware { @@ -41,6 +47,7 @@ public class PySciViewAction extends ToggleAction implements DumbAware { public static final String ACTION_ID = "PySciView"; private ToolWindowType myDataViewType = ToolWindowType.FLOATING; + private Color myDocumentationBackgroundColor; public PySciViewAction() { super(TEXT_SCI_VIEW); @@ -86,6 +93,10 @@ public class PySciViewAction extends ToggleAction implements DumbAware { private void showDocumentationToolwindow(Project project, PsiElement element) { final String showInToolWindowProperty = DocumentationManager.getInstance(project).getShowInToolWindowProperty(); + EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme(); + myDocumentationBackgroundColor = scheme.getColor(COLOR_KEY); + scheme.setColor(COLOR_KEY, UIUtil.getEditorPaneBackground()); + PropertiesComponent.getInstance().setValue(showInToolWindowProperty, true); PropertiesComponent.getInstance().setValue(DocumentationManager.getInstance(project).getAutoUpdateEnabledProperty(), true); @@ -104,6 +115,8 @@ public class PySciViewAction extends ToggleAction implements DumbAware { private void restoreDocumentationPopup(Project project) { PropertiesComponent.getInstance().setValue(DocumentationManager.getInstance(project).getAutoUpdateEnabledProperty(), false); + EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme(); + scheme.setColor(COLOR_KEY, myDocumentationBackgroundColor); final ToolWindow window = ToolWindowManager.getInstance(project).getToolWindow(ToolWindowId.DOCUMENTATION); if (window != null) { From 236c97fcf0ff67b007aa748970ce04628cdae56d Mon Sep 17 00:00:00 2001 From: Ekaterina Tuzova Date: Tue, 22 Aug 2017 21:11:04 +0300 Subject: [PATCH 10/22] do not autohide data view toolwindow in sci mode --- python/src/com/jetbrains/python/actions/PySciViewAction.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/python/src/com/jetbrains/python/actions/PySciViewAction.java b/python/src/com/jetbrains/python/actions/PySciViewAction.java index 0fc149ae3727..c131e911af75 100644 --- a/python/src/com/jetbrains/python/actions/PySciViewAction.java +++ b/python/src/com/jetbrains/python/actions/PySciViewAction.java @@ -47,7 +47,7 @@ public class PySciViewAction extends ToggleAction implements DumbAware { public static final String ACTION_ID = "PySciView"; private ToolWindowType myDataViewType = ToolWindowType.FLOATING; - private Color myDocumentationBackgroundColor; + private Color myDocumentationBackgroundColor = EditorColorsManager.getInstance().getGlobalScheme().getColor(COLOR_KEY); public PySciViewAction() { super(TEXT_SCI_VIEW); @@ -134,12 +134,14 @@ public class PySciViewAction extends ToggleAction implements DumbAware { ToolWindow dataViewToolWindow = ToolWindowManager.getInstance(project).getToolWindow(DATA_VIEWER_ID); myDataViewType = dataViewToolWindow.getType(); dataViewToolWindow.setType(ToolWindowType.DOCKED, null); + dataViewToolWindow.setAutoHide(false); dataViewToolWindow.setShowStripeButton(true); } private void hideDataViewer(Project project) { final ToolWindow dataViewer = ToolWindowManager.getInstance(project).getToolWindow(DATA_VIEWER_ID); dataViewer.setType(myDataViewType, null); + dataViewer.setAutoHide(true); dataViewer.setShowStripeButton(false); } From 696e1f3530bd08874d2cf076b486621276d9e90f Mon Sep 17 00:00:00 2001 From: Ekaterina Tuzova Date: Tue, 22 Aug 2017 21:11:33 +0300 Subject: [PATCH 11/22] set nearest font size for the documentation toolwindow --- .../documentation/DocumentationComponent.java | 2 +- .../python/actions/PySciViewAction.java | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/platform/lang-impl/src/com/intellij/codeInsight/documentation/DocumentationComponent.java b/platform/lang-impl/src/com/intellij/codeInsight/documentation/DocumentationComponent.java index 0e695ee8c1ca..df91737f02f5 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/documentation/DocumentationComponent.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/documentation/DocumentationComponent.java @@ -104,7 +104,7 @@ public class DocumentationComponent extends JPanel implements Disposable, DataPr private SmartPsiElementPointer myElement; private long myModificationCount; - private static final String QUICK_DOC_FONT_SIZE_PROPERTY = "quick.doc.font.size"; + public static final String QUICK_DOC_FONT_SIZE_PROPERTY = "quick.doc.font.size"; private final Stack myBackStack = new Stack<>(); private final Stack myForwardStack = new Stack<>(); diff --git a/python/src/com/jetbrains/python/actions/PySciViewAction.java b/python/src/com/jetbrains/python/actions/PySciViewAction.java index c131e911af75..7e2273719dff 100644 --- a/python/src/com/jetbrains/python/actions/PySciViewAction.java +++ b/python/src/com/jetbrains/python/actions/PySciViewAction.java @@ -23,6 +23,7 @@ import com.intellij.openapi.actionSystem.Presentation; import com.intellij.openapi.actionSystem.ToggleAction; import com.intellij.openapi.editor.colors.EditorColorsManager; import com.intellij.openapi.editor.colors.EditorColorsScheme; +import com.intellij.openapi.options.FontSize; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.popup.JBPopup; @@ -36,6 +37,7 @@ import org.jetbrains.annotations.NotNull; import java.awt.*; import static com.intellij.codeInsight.documentation.DocumentationComponent.COLOR_KEY; +import static com.intellij.codeInsight.documentation.DocumentationComponent.QUICK_DOC_FONT_SIZE_PROPERTY; import static com.jetbrains.python.debugger.containerview.PyDataView.DATA_VIEWER_ID; public class PySciViewAction extends ToggleAction implements DumbAware { @@ -103,6 +105,8 @@ public class PySciViewAction extends ToggleAction implements DumbAware { if (element != null) { DocumentationManager.getInstance(project).showJavaDocInfo(element, element); } + setDocFontSize(); + final ToolWindow window = ToolWindowManager.getInstance(project).getToolWindow(ToolWindowId.DOCUMENTATION); if (window != null) { window.setAnchor(ToolWindowAnchor.RIGHT, null); @@ -113,6 +117,21 @@ public class PySciViewAction extends ToggleAction implements DumbAware { } } + private static void setDocFontSize() { + EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme(); + final int editorFontSize = scheme.getEditorFontSize(); + final FontSize[] values = FontSize.values(); + FontSize fontSize = FontSize.MEDIUM; + for (FontSize value : values) { + if (value.getSize() > editorFontSize) { + break; + } + fontSize = value; + } + + PropertiesComponent.getInstance().setValue(QUICK_DOC_FONT_SIZE_PROPERTY, String.valueOf(fontSize.toString())); + } + private void restoreDocumentationPopup(Project project) { PropertiesComponent.getInstance().setValue(DocumentationManager.getInstance(project).getAutoUpdateEnabledProperty(), false); EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme(); From 50376fdaf3c14d2bc24d8279c02817c247cc60ad Mon Sep 17 00:00:00 2001 From: Ekaterina Tuzova Date: Tue, 22 Aug 2017 21:11:57 +0300 Subject: [PATCH 12/22] turn on/off show command line afterwards in scientific mode --- .../python/actions/PySciViewAction.java | 16 ++++++++++++++++ .../python/run/PythonRunConfiguration.java | 2 -- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/python/src/com/jetbrains/python/actions/PySciViewAction.java b/python/src/com/jetbrains/python/actions/PySciViewAction.java index 7e2273719dff..eb900f63169c 100644 --- a/python/src/com/jetbrains/python/actions/PySciViewAction.java +++ b/python/src/com/jetbrains/python/actions/PySciViewAction.java @@ -16,6 +16,9 @@ package com.jetbrains.python.actions; import com.intellij.codeInsight.documentation.DocumentationManager; +import com.intellij.execution.RunManager; +import com.intellij.execution.RunnerAndConfigurationSettings; +import com.intellij.execution.configurations.RunConfiguration; import com.intellij.ide.util.PropertiesComponent; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.CommonDataKeys; @@ -32,6 +35,8 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.PsiManager; import com.intellij.util.ui.UIUtil; import com.jetbrains.python.console.PythonConsoleToolWindow; +import com.jetbrains.python.run.PythonConfigurationType; +import com.jetbrains.python.run.PythonRunConfiguration; import org.jetbrains.annotations.NotNull; import java.awt.*; @@ -72,11 +77,22 @@ public class PySciViewAction extends ToggleAction implements DumbAware { showConsoleToolwindow(project); showDocumentationToolwindow(project, element); showDataViewAsToolwindow(project); + showCommandLineInRunConfiguration(project, true); } else { hideConsoleToolwindow(project); restoreDocumentationPopup(project); hideDataViewer(project); + showCommandLineInRunConfiguration(project, false); + } + } + + private static void showCommandLineInRunConfiguration(Project project, boolean show) { + final RunnerAndConfigurationSettings template = + RunManager.getInstance(project).getConfigurationTemplate(PythonConfigurationType.getInstance().getFactory()); + final RunConfiguration configuration = template.getConfiguration(); + if (configuration instanceof PythonRunConfiguration) { + ((PythonRunConfiguration)configuration).setShowCommandLineAfterwards(show); } } diff --git a/python/src/com/jetbrains/python/run/PythonRunConfiguration.java b/python/src/com/jetbrains/python/run/PythonRunConfiguration.java index 0a86309dd5cd..ca1d42f6a9a1 100644 --- a/python/src/com/jetbrains/python/run/PythonRunConfiguration.java +++ b/python/src/com/jetbrains/python/run/PythonRunConfiguration.java @@ -19,11 +19,9 @@ import com.intellij.execution.ExecutionException; import com.intellij.execution.Executor; import com.intellij.execution.configurations.*; import com.intellij.execution.runners.ExecutionEnvironment; -import com.intellij.openapi.components.PathMacroManager; import com.intellij.openapi.options.SettingsEditor; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.util.InvalidDataException; import com.intellij.openapi.util.JDOMExternalizerUtil; import com.intellij.openapi.util.WriteExternalException; import com.intellij.openapi.util.io.FileUtil; From 3c7bd8a29285acb903bdda07810f457b651c7614 Mon Sep 17 00:00:00 2001 From: Ekaterina Tuzova Date: Wed, 23 Aug 2017 14:03:04 +0300 Subject: [PATCH 13/22] add run file in console action for python --- python/src/META-INF/python-core-common.xml | 4 + .../actions/PyRunFileInConsoleAction.java | 73 +++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 python/src/com/jetbrains/python/actions/PyRunFileInConsoleAction.java diff --git a/python/src/META-INF/python-core-common.xml b/python/src/META-INF/python-core-common.xml index 921f601e99b6..6b1921eb801c 100644 --- a/python/src/META-INF/python-core-common.xml +++ b/python/src/META-INF/python-core-common.xml @@ -809,6 +809,10 @@ + + + + diff --git a/python/src/com/jetbrains/python/actions/PyRunFileInConsoleAction.java b/python/src/com/jetbrains/python/actions/PyRunFileInConsoleAction.java new file mode 100644 index 000000000000..d0817b6a0fd2 --- /dev/null +++ b/python/src/com/jetbrains/python/actions/PyRunFileInConsoleAction.java @@ -0,0 +1,73 @@ +/* + * Copyright 2000-2017 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.jetbrains.python.actions; + +import com.intellij.execution.ExecutionManager; +import com.intellij.execution.RunManager; +import com.intellij.execution.RunnerAndConfigurationSettings; +import com.intellij.execution.actions.ConfigurationContext; +import com.intellij.execution.actions.ConfigurationFromContext; +import com.intellij.execution.actions.RunConfigurationProducer; +import com.intellij.execution.executors.DefaultRunExecutor; +import com.intellij.execution.runners.ExecutionEnvironmentBuilder; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.actionSystem.Presentation; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiFile; +import com.jetbrains.python.psi.PyFile; +import com.jetbrains.python.run.PythonRunConfigurationParams; +import com.jetbrains.python.run.PythonRunConfigurationProducer; +import org.jetbrains.annotations.NotNull; + +public class PyRunFileInConsoleAction extends AnAction implements DumbAware { + public PyRunFileInConsoleAction() { + super("Run File in Console"); + } + + @Override + public void update(@NotNull AnActionEvent e) { + super.update(e); + final Project project = e.getProject(); + final PsiFile psiFile = CommonDataKeys.PSI_FILE.getData(e.getDataContext()); + final Presentation presentation = e.getPresentation(); + presentation.setEnabledAndVisible(project != null && psiFile instanceof PyFile); + } + + @Override + public void actionPerformed(AnActionEvent e) { + final PsiFile file = CommonDataKeys.PSI_FILE.getData(e.getDataContext()); + if (file == null) return; + final Project project = e.getProject(); + if (project == null) return; + final ConfigurationContext context = ConfigurationContext.getFromContext(e.getDataContext()); + final ConfigurationFromContext fromContext = + RunConfigurationProducer.getInstance(PythonRunConfigurationProducer.class).createConfigurationFromContext(context); + if (fromContext == null) return; + final RunnerAndConfigurationSettings settings = fromContext.getConfigurationSettings(); + final PythonRunConfigurationParams configuration = (PythonRunConfigurationParams)settings.getConfiguration(); + configuration.setShowCommandLineAfterwards(true); + RunManager runManager = RunManager.getInstance(project); + runManager.setTemporaryConfiguration(settings); + runManager.setSelectedConfiguration(settings); + ExecutionEnvironmentBuilder builder = ExecutionEnvironmentBuilder.createOrNull(DefaultRunExecutor.getRunExecutorInstance(), settings); + if (builder != null) { + ExecutionManager.getInstance(project).restartRunProfile(builder.build()); + } + } +} From aa9f4ff2a98b073848113a63fd0299b5effc39b6 Mon Sep 17 00:00:00 2001 From: Ekaterina Tuzova Date: Wed, 30 Aug 2017 18:31:57 +0300 Subject: [PATCH 14/22] load and render python stdlib in scientific mode --- python/python-community.iml | 13 +++- .../python/actions/PySciViewAction.java | 12 ++++ .../PyStdlibDocumentationLinkProvider.java | 3 +- .../PyIntegratedToolsConfigurable.form | 10 ++- .../PyIntegratedToolsConfigurable.java | 8 ++- .../PyDocumentationSettings.java | 9 +++ .../PythonDocumentationProvider.java | 62 ++++++++++++++++++- 7 files changed, 110 insertions(+), 7 deletions(-) diff --git a/python/python-community.iml b/python/python-community.iml index b5f980a97e1a..a1d6fc61d2b9 100644 --- a/python/python-community.iml +++ b/python/python-community.iml @@ -26,6 +26,15 @@ + + + + + + + + + +
- - + \ No newline at end of file diff --git a/python/src/com/jetbrains/python/actions/PySciViewAction.java b/python/src/com/jetbrains/python/actions/PySciViewAction.java index eb900f63169c..33364d0ee0b0 100644 --- a/python/src/com/jetbrains/python/actions/PySciViewAction.java +++ b/python/src/com/jetbrains/python/actions/PySciViewAction.java @@ -26,6 +26,8 @@ import com.intellij.openapi.actionSystem.Presentation; import com.intellij.openapi.actionSystem.ToggleAction; import com.intellij.openapi.editor.colors.EditorColorsManager; import com.intellij.openapi.editor.colors.EditorColorsScheme; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleUtilCore; import com.intellij.openapi.options.FontSize; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; @@ -35,6 +37,7 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.PsiManager; import com.intellij.util.ui.UIUtil; import com.jetbrains.python.console.PythonConsoleToolWindow; +import com.jetbrains.python.documentation.PyDocumentationSettings; import com.jetbrains.python.run.PythonConfigurationType; import com.jetbrains.python.run.PythonRunConfiguration; import org.jetbrains.annotations.NotNull; @@ -78,12 +81,21 @@ public class PySciViewAction extends ToggleAction implements DumbAware { showDocumentationToolwindow(project, element); showDataViewAsToolwindow(project); showCommandLineInRunConfiguration(project, true); + renderExternalDocumentation(element, true); } else { hideConsoleToolwindow(project); restoreDocumentationPopup(project); hideDataViewer(project); showCommandLineInRunConfiguration(project, false); + renderExternalDocumentation(element, false); + } + } + + private static void renderExternalDocumentation(PsiElement element, boolean render) { + final Module module = ModuleUtilCore.findModuleForPsiElement(element); + if (module != null) { + PyDocumentationSettings.getInstance(module).setRenderExternalDocumentation(render); } } diff --git a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibDocumentationLinkProvider.java b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibDocumentationLinkProvider.java index e982087327e5..9325337c3ecc 100644 --- a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibDocumentationLinkProvider.java +++ b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibDocumentationLinkProvider.java @@ -22,6 +22,7 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFileSystemItem; import com.intellij.psi.PsiNamedElement; +import com.intellij.psi.util.QualifiedName; import com.jetbrains.python.PyNames; import com.jetbrains.python.documentation.PythonDocumentationLinkProvider; import com.jetbrains.python.documentation.PythonDocumentationProvider; @@ -29,7 +30,6 @@ import com.jetbrains.python.psi.PyClass; import com.jetbrains.python.psi.PyFile; import com.jetbrains.python.psi.PyFunction; import com.jetbrains.python.psi.impl.PyBuiltinCache; -import com.intellij.psi.util.QualifiedName; import com.jetbrains.python.psi.resolve.QualifiedNameFinder; import com.jetbrains.python.sdk.PythonSdkType; @@ -544,6 +544,7 @@ public class PyStdlibDocumentationLinkProvider implements PythonDocumentationLin @Override public String getExternalDocumentationUrl(PsiElement element, PsiElement originalElement) { + if (PyBuiltinCache.getInstance(element).isBuiltin(element)) return null; PsiFileSystemItem file = element instanceof PsiFileSystemItem ? (PsiFileSystemItem) element : element.getContainingFile(); if (PyNames.INIT_DOT_PY.equals(file.getName())) { file = file.getParent(); diff --git a/python/src/com/jetbrains/python/configuration/PyIntegratedToolsConfigurable.form b/python/src/com/jetbrains/python/configuration/PyIntegratedToolsConfigurable.form index 5aacd414fb03..50e117174f63 100644 --- a/python/src/com/jetbrains/python/configuration/PyIntegratedToolsConfigurable.form +++ b/python/src/com/jetbrains/python/configuration/PyIntegratedToolsConfigurable.form @@ -49,7 +49,7 @@ - + @@ -82,6 +82,14 @@ + + + + + + + + diff --git a/python/src/com/jetbrains/python/configuration/PyIntegratedToolsConfigurable.java b/python/src/com/jetbrains/python/configuration/PyIntegratedToolsConfigurable.java index dc3d88ca5cd3..c519eda530ce 100644 --- a/python/src/com/jetbrains/python/configuration/PyIntegratedToolsConfigurable.java +++ b/python/src/com/jetbrains/python/configuration/PyIntegratedToolsConfigurable.java @@ -39,7 +39,6 @@ import com.intellij.ui.ListCellRendererWrapper; import com.intellij.util.FileContentUtil; import com.intellij.util.FileContentUtilCore; import com.jetbrains.python.PyBundle; -import com.jetbrains.python.PyNames; import com.jetbrains.python.PythonFileType; import com.jetbrains.python.ReSTService; import com.jetbrains.python.documentation.PyDocumentationSettings; @@ -78,6 +77,7 @@ public class PyIntegratedToolsConfigurable implements SearchableConfigurable { private JCheckBox analyzeDoctest; private JPanel myDocStringsPanel; private JPanel myRestPanel; + private JCheckBox renderExternal; public PyIntegratedToolsConfigurable(@NotNull Module module) { myModule = module; @@ -99,6 +99,7 @@ public class PyIntegratedToolsConfigurable implements SearchableConfigurable { myWorkDir.setText(service.getWorkdir()); txtIsRst.setSelected(service.txtIsRst()); analyzeDoctest.setSelected(myDocumentationSettings.isAnalyzeDoctest()); + renderExternal.setSelected(myDocumentationSettings.isRenderExternalDocumentation()); myRequirementsPathField.addBrowseFolderListener("Choose path to the package requirements file:", null, myProject, FileChooserDescriptorFactory.createSingleLocalFileDescriptor()); myRequirementsPathField.setText(getRequirementsPath()); @@ -207,6 +208,9 @@ public class PyIntegratedToolsConfigurable implements SearchableConfigurable { if (analyzeDoctest.isSelected() != myDocumentationSettings.isAnalyzeDoctest()) { return true; } + if (renderExternal.isSelected() != myDocumentationSettings.isRenderExternalDocumentation()) { + return true; + } if (!ReSTService.getInstance(myModule).getWorkdir().equals(myWorkDir.getText())) { return true; } @@ -235,6 +239,7 @@ public class PyIntegratedToolsConfigurable implements SearchableConfigurable { FileContentUtil.reparseFiles(myProject, Lists.newArrayList(files), false); } myModel.apply(); + myDocumentationSettings.setRenderExternalDocumentation(renderExternal.isSelected()); myDocumentationSettings.setFormat((DocStringFormat)myDocstringFormatComboBox.getSelectedItem()); final ReSTService reSTService = ReSTService.getInstance(myModule); reSTService.setWorkdir(myWorkDir.getText()); @@ -271,6 +276,7 @@ public class PyIntegratedToolsConfigurable implements SearchableConfigurable { myWorkDir.setText(ReSTService.getInstance(myModule).getWorkdir()); txtIsRst.setSelected(ReSTService.getInstance(myModule).txtIsRst()); analyzeDoctest.setSelected(myDocumentationSettings.isAnalyzeDoctest()); + renderExternal.setSelected(myDocumentationSettings.isRenderExternalDocumentation()); myRequirementsPathField.setText(getRequirementsPath()); } diff --git a/python/src/com/jetbrains/python/documentation/PyDocumentationSettings.java b/python/src/com/jetbrains/python/documentation/PyDocumentationSettings.java index fd43b67011b1..9e21a47b6c43 100644 --- a/python/src/com/jetbrains/python/documentation/PyDocumentationSettings.java +++ b/python/src/com/jetbrains/python/documentation/PyDocumentationSettings.java @@ -47,6 +47,7 @@ public class PyDocumentationSettings implements PersistentStateComponent docUrls) { - return null; + final Module module = ModuleUtilCore.findModuleForPsiElement(element); + if (module != null && !PyDocumentationSettings.getInstance(module).isRenderExternalDocumentation()) return null; + return ApplicationManager.getApplication().runReadAction((Computable)() -> { + PsiFileSystemItem file = element instanceof PsiFileSystemItem ? (PsiFileSystemItem)element : element.getContainingFile(); + if (file == null) return null; + if (PyNames.INIT_DOT_PY.equals(file.getName())) { + file = file.getParent(); + assert file != null; + } + final Sdk sdk = PyBuiltinCache.findSdkForFile(file); + if (sdk == null) { + return null; + } + + final QualifiedName moduleQName = QualifiedNameFinder.findCanonicalImportPath(element, element); + if (moduleQName == null) { + return null; + } + PsiNamedElement namedElement = (element instanceof PsiNamedElement && !(element instanceof PsiFileSystemItem)) + ? (PsiNamedElement)element + : null; + if (namedElement instanceof PyFunction && PyNames.INIT.equals(namedElement.getName())) { + final PyClass containingClass = ((PyFunction)namedElement).getContainingClass(); + if (containingClass != null) { + namedElement = containingClass; + } + } + final PyStdlibDocumentationLinkProvider stdlibDocumentationLinkProvider = + Extensions.findExtension(PythonDocumentationLinkProvider.EP_NAME, PyStdlibDocumentationLinkProvider.class); + final String url = stdlibDocumentationLinkProvider.getExternalDocumentationUrl(element, element); + if (url == null) { + return null; + } + + try { + final Document document = Jsoup.parse(new URL(url), 1000); + final String elementId = namedElement != null ? moduleQName + "." + namedElement.getName() : "module-" + moduleQName; + document.select("a.headerlink").remove(); + final Elements parents = document.getElementsByAttributeValue("id", elementId).parents(); + if (parents.isEmpty()) { + return document.toString(); + } + return parents.get(0).toString(); + } + catch (MalformedURLException ignored) { + } + catch (IOException ignored) { + } + return null; + }); } @Override From bbf700400bbf88a1892472f191a970021cfce857 Mon Sep 17 00:00:00 2001 From: Ekaterina Tuzova Date: Thu, 31 Aug 2017 12:28:25 +0300 Subject: [PATCH 15/22] build python function description with better attributes --- .../jetbrains/python/documentation/PyDocumentationBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/src/com/jetbrains/python/documentation/PyDocumentationBuilder.java b/python/src/com/jetbrains/python/documentation/PyDocumentationBuilder.java index 45bf3783cef2..8f38aff20ce2 100644 --- a/python/src/com/jetbrains/python/documentation/PyDocumentationBuilder.java +++ b/python/src/com/jetbrains/python/documentation/PyDocumentationBuilder.java @@ -79,7 +79,7 @@ public class PyDocumentationBuilder { myBody = new ChainIterable<>(); myEpilog = new ChainIterable<>(); - myResult.add(myProlog).addWith(TagCode, myBody).add(myEpilog); // pre-assemble; then add stuff to individual cats as needed + myResult.add(myProlog).addWith(TagCode.withAttribute("class", "descclassname"), myBody).add(myEpilog); // pre-assemble; then add stuff to individual cats as needed myResult = wrapInTag("html", wrapInTag("body", myResult)); myReassignmentChain = new ChainIterable<>(); } From fadfcaab3624b1380c45b6618c5bc1f9ca81c63c Mon Sep 17 00:00:00 2001 From: Ekaterina Tuzova Date: Thu, 31 Aug 2017 14:12:43 +0300 Subject: [PATCH 16/22] do not wrap code in additional attribute --- .../jetbrains/python/documentation/PyDocumentationBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/src/com/jetbrains/python/documentation/PyDocumentationBuilder.java b/python/src/com/jetbrains/python/documentation/PyDocumentationBuilder.java index 8f38aff20ce2..45bf3783cef2 100644 --- a/python/src/com/jetbrains/python/documentation/PyDocumentationBuilder.java +++ b/python/src/com/jetbrains/python/documentation/PyDocumentationBuilder.java @@ -79,7 +79,7 @@ public class PyDocumentationBuilder { myBody = new ChainIterable<>(); myEpilog = new ChainIterable<>(); - myResult.add(myProlog).addWith(TagCode.withAttribute("class", "descclassname"), myBody).add(myEpilog); // pre-assemble; then add stuff to individual cats as needed + myResult.add(myProlog).addWith(TagCode, myBody).add(myEpilog); // pre-assemble; then add stuff to individual cats as needed myResult = wrapInTag("html", wrapInTag("body", myResult)); myReassignmentChain = new ChainIterable<>(); } From 8116bf7c1ca173ffbc02050258c64507802f728f Mon Sep 17 00:00:00 2001 From: Ekaterina Tuzova Date: Thu, 31 Aug 2017 14:13:24 +0300 Subject: [PATCH 17/22] PY-25778 Show documentation on import target expression --- .../com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java index 8a1a518df04e..3013196c011d 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java @@ -588,6 +588,10 @@ public class PyTargetExpressionImpl extends PyBaseElementImpl Date: Thu, 31 Aug 2017 15:12:00 +0300 Subject: [PATCH 18/22] PY-25779 Show documentation for method if caret is placed inside parameters list --- .../PythonDocumentationProvider.java | 22 ++++++++++++++++--- python/testData/quickdoc/ArgumentList.html | 1 + python/testData/quickdoc/ArgumentList.py | 6 +++++ python/testData/quickdoc/BuiltinLen.html | 2 +- .../com/jetbrains/python/PyQuickDocTest.java | 8 +++++++ 5 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 python/testData/quickdoc/ArgumentList.html create mode 100644 python/testData/quickdoc/ArgumentList.py diff --git a/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java b/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java index bb32343dda0d..2c3758a0946d 100644 --- a/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java +++ b/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java @@ -31,9 +31,11 @@ import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.Computable; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; +import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.QualifiedName; import com.jetbrains.python.PyNames; +import com.jetbrains.python.PyTokenTypes; import com.jetbrains.python.PythonDialectsTokenSetProvider; import com.jetbrains.python.codeInsight.stdlib.PyStdlibDocumentationLinkProvider; import com.jetbrains.python.console.PydevConsoleRunner; @@ -547,9 +549,23 @@ public class PythonDocumentationProvider extends AbstractDocumentationProvider i public PsiElement getCustomDocumentationElement(@NotNull Editor editor, @NotNull PsiFile file, @Nullable PsiElement contextElement) { - if (contextElement != null && - PythonDialectsTokenSetProvider.INSTANCE.getKeywordTokens().contains(contextElement.getNode().getElementType())) { - return contextElement; + if (contextElement != null) { + final IElementType elementType = contextElement.getNode().getElementType(); + if (PythonDialectsTokenSetProvider.INSTANCE.getKeywordTokens().contains(elementType)) { + return contextElement; + } + if (PyTokenTypes.LPAR == elementType || PyTokenTypes.RPAR == elementType) { + final PyCallExpression expression = PsiTreeUtil.getParentOfType(contextElement, PyCallExpression.class); + if (expression != null) { + final PyExpression callee = expression.getCallee(); + if (callee != null) { + final PsiReference reference = callee.getReference(); + if (reference != null) { + return reference.resolve(); + } + } + } + } } return super.getCustomDocumentationElement(editor, file, contextElement); } diff --git a/python/testData/quickdoc/ArgumentList.html b/python/testData/quickdoc/ArgumentList.html new file mode 100644 index 000000000000..e4cfe0fd4e36 --- /dev/null +++ b/python/testData/quickdoc/ArgumentList.html @@ -0,0 +1 @@ +def foo()
Inferred type: () -> None

Doc of foo.
\ No newline at end of file diff --git a/python/testData/quickdoc/ArgumentList.py b/python/testData/quickdoc/ArgumentList.py new file mode 100644 index 000000000000..6aff48527784 --- /dev/null +++ b/python/testData/quickdoc/ArgumentList.py @@ -0,0 +1,6 @@ +# directly in function +def foo(): + "Doc of foo." + pass + +foo() diff --git a/python/testData/quickdoc/BuiltinLen.html b/python/testData/quickdoc/BuiltinLen.html index db51bb72f7e6..805ad69fbbd3 100644 --- a/python/testData/quickdoc/BuiltinLen.html +++ b/python/testData/quickdoc/BuiltinLen.html @@ -1 +1 @@ -def len(o: Sized)
Inferred type: (o: Sized) -> int


len(object) -> integer

Return the number of items of a sequence or collection.

External documentation:
http://docs.python.org/2.7 Mock SDK/library/__builtin__.html#__builtin__.len +def len(o: Sized)
Inferred type: (o: Sized) -> int


len(object) -> integer

Return the number of items of a sequence or collection.
diff --git a/python/testSrc/com/jetbrains/python/PyQuickDocTest.java b/python/testSrc/com/jetbrains/python/PyQuickDocTest.java index 1e7823e065f5..ce5ae9d67346 100644 --- a/python/testSrc/com/jetbrains/python/PyQuickDocTest.java +++ b/python/testSrc/com/jetbrains/python/PyQuickDocTest.java @@ -292,6 +292,14 @@ public class PyQuickDocTest extends LightMarkedTestCase { checkHTMLOnly(); } + public void testArgumentList() { + Map marks = loadTest(); + final PsiElement originalElement = marks.get(""); + + final PsiElement element = myProvider.getCustomDocumentationElement(myFixture.getEditor(), myFile, originalElement); + checkByHTML(myProvider.generateDoc(element, originalElement)); + } + public void testReferenceToMethodQualifiedWithInstance() { checkHTMLOnly(); } From 99c0e6c3f40068ce1c3853590fcdc3f8fe2144da Mon Sep 17 00:00:00 2001 From: Ekaterina Tuzova Date: Thu, 31 Aug 2017 15:17:25 +0300 Subject: [PATCH 19/22] PY-24854 Allow Quick Documentation invocation when caret is placed inside docstring --- .../python/documentation/PythonDocumentationProvider.java | 4 ++++ python/testData/quickdoc/Docstring.html | 1 + python/testData/quickdoc/Docstring.py | 6 ++++++ python/testSrc/com/jetbrains/python/PyQuickDocTest.java | 8 ++++++++ 4 files changed, 19 insertions(+) create mode 100644 python/testData/quickdoc/Docstring.html create mode 100644 python/testData/quickdoc/Docstring.py diff --git a/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java b/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java index 2c3758a0946d..565b39361545 100644 --- a/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java +++ b/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java @@ -566,6 +566,10 @@ public class PythonDocumentationProvider extends AbstractDocumentationProvider i } } } + if (PyTokenTypes.DOCSTRING == elementType) { + final PyDocStringOwner docstringOwner = PsiTreeUtil.getParentOfType(contextElement, PyDocStringOwner.class); + if (docstringOwner != null) return docstringOwner; + } } return super.getCustomDocumentationElement(editor, file, contextElement); } diff --git a/python/testData/quickdoc/Docstring.html b/python/testData/quickdoc/Docstring.html new file mode 100644 index 000000000000..e4cfe0fd4e36 --- /dev/null +++ b/python/testData/quickdoc/Docstring.html @@ -0,0 +1 @@ +def foo()
Inferred type: () -> None

Doc of foo.
\ No newline at end of file diff --git a/python/testData/quickdoc/Docstring.py b/python/testData/quickdoc/Docstring.py new file mode 100644 index 000000000000..a703742ebbb4 --- /dev/null +++ b/python/testData/quickdoc/Docstring.py @@ -0,0 +1,6 @@ +# directly in function +def foo(): + "Doc of foo." + pass + +foo() diff --git a/python/testSrc/com/jetbrains/python/PyQuickDocTest.java b/python/testSrc/com/jetbrains/python/PyQuickDocTest.java index ce5ae9d67346..8f0599d55795 100644 --- a/python/testSrc/com/jetbrains/python/PyQuickDocTest.java +++ b/python/testSrc/com/jetbrains/python/PyQuickDocTest.java @@ -300,6 +300,14 @@ public class PyQuickDocTest extends LightMarkedTestCase { checkByHTML(myProvider.generateDoc(element, originalElement)); } + public void testDocstring() { + Map marks = loadTest(); + final PsiElement originalElement = marks.get(""); + + final PsiElement element = myProvider.getCustomDocumentationElement(myFixture.getEditor(), myFile, originalElement); + checkByHTML(myProvider.generateDoc(element, originalElement)); + } + public void testReferenceToMethodQualifiedWithInstance() { checkHTMLOnly(); } From 6fdfa74facd311318bc0527325dbbaf0f3c2bed2 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Fri, 1 Sep 2017 17:30:01 +0700 Subject: [PATCH 20/22] LiveVariablesAnalyzer#findLiveVars: clone or'ed BitSet as well We should ensure that runDFA lambda never returns an object which will be mutated afterwards as this object used as part of `processed` Set key (inside InstructionState). --- .../codeInspection/dataFlow/LiveVariablesAnalyzer.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/LiveVariablesAnalyzer.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/LiveVariablesAnalyzer.java index 0c559889e31d..18f2f5f36fc0 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/LiveVariablesAnalyzer.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/LiveVariablesAnalyzer.java @@ -146,8 +146,9 @@ public class LiveVariablesAnalyzer { BitSet set = result.get(instruction); if (set != null) { set.or(liveVars); - return set; - } else { + return (BitSet)set.clone(); + } + else if (!liveVars.isEmpty()) { result.put((FinishElementInstruction)instruction, (BitSet)liveVars.clone()); } } From ac1016dc32993314a866b22c6261e5c058b07684 Mon Sep 17 00:00:00 2001 From: Dennis Ushakov Date: Fri, 1 Sep 2017 13:36:53 +0300 Subject: [PATCH 21/22] don't forget to cleanup whitespaces/newline keys (WEB-24694) --- .../configmanagement/DocumentSettingsManager.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/editorconfig/src/org/editorconfig/configmanagement/DocumentSettingsManager.java b/plugins/editorconfig/src/org/editorconfig/configmanagement/DocumentSettingsManager.java index 61246f56fbb5..166961f61c1e 100644 --- a/plugins/editorconfig/src/org/editorconfig/configmanagement/DocumentSettingsManager.java +++ b/plugins/editorconfig/src/org/editorconfig/configmanagement/DocumentSettingsManager.java @@ -72,7 +72,10 @@ public class DocumentSettingsManager extends FileDocumentManagerAdapter { private void applyConfigValueToUserData(VirtualFile file, Key userDataKey, String editorConfigKey, String configValue, Map configMap) { - if (configValue.isEmpty()) return; + if (configValue.isEmpty()) { + file.putUserData(userDataKey, null); + return; + } final T data = configMap.get(configValue); if (data == null) { From 21d0c733590db7cff2965d79f29a7929d07d61c0 Mon Sep 17 00:00:00 2001 From: Eugene Zhuravlev Date: Fri, 1 Sep 2017 14:58:25 +0300 Subject: [PATCH 22/22] prefer in-process compilation in jps build tests --- .../testSrc/org/jetbrains/jps/builders/JpsBuildTestCase.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/jps/jps-builders/testSrc/org/jetbrains/jps/builders/JpsBuildTestCase.java b/jps/jps-builders/testSrc/org/jetbrains/jps/builders/JpsBuildTestCase.java index c20ba5d2a76b..eeae765574e3 100644 --- a/jps/jps-builders/testSrc/org/jetbrains/jps/builders/JpsBuildTestCase.java +++ b/jps/jps-builders/testSrc/org/jetbrains/jps/builders/JpsBuildTestCase.java @@ -47,6 +47,8 @@ import org.jetbrains.jps.indices.impl.IgnoredFileIndexImpl; import org.jetbrains.jps.indices.impl.ModuleExcludeIndexImpl; import org.jetbrains.jps.model.*; import org.jetbrains.jps.model.java.*; +import org.jetbrains.jps.model.java.compiler.JavaCompilers; +import org.jetbrains.jps.model.java.compiler.JpsJavaCompilerConfiguration; import org.jetbrains.jps.model.library.JpsOrderRootType; import org.jetbrains.jps.model.library.JpsTypedLibrary; import org.jetbrains.jps.model.library.sdk.JpsSdk; @@ -229,6 +231,8 @@ public abstract class JpsBuildTestCase extends UsefulTestCase { allPathVariables.put(PathMacroUtil.APPLICATION_HOME_DIR, PathManager.getHomePath()); allPathVariables.putAll(getAdditionalPathVariables()); JpsProjectLoader.loadProject(myProject, allPathVariables, fullProjectPath); + final JpsJavaCompilerConfiguration config = JpsJavaExtensionService.getInstance().getCompilerConfiguration(myProject); + config.getCompilerOptions(JavaCompilers.JAVAC_ID).PREFER_TARGET_JDK_COMPILER = false; } catch (IOException e) { throw new RuntimeException(e);