From f28dca1fd5e25328845bafa29252563579689ed5 Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Wed, 11 May 2016 22:43:00 +0300 Subject: [PATCH] Supported forEach --- .../intentions/loopToCallChain/interfaces.kt | 3 +- .../loopToCallChain/matchAndConvert.kt | 7 ++- .../result/ForEachTransformation.kt | 57 +++++++++++++++++++ .../firstOrNull_returnNotNullIfNone2.kt | 1 - .../firstOrNull_returnNotNullIfNone2.kt.after | 7 +++ .../intentions/loopToCallChain/forEach.kt | 9 +++ .../loopToCallChain/forEach.kt.after | 7 +++ .../loopToCallChain/forEach_notAvailable.kt | 7 +++ .../resultCollectionUsedInsideLoop.kt | 1 - .../resultCollectionUsedInsideLoop.kt.after | 10 ++++ 10 files changed, 104 insertions(+), 5 deletions(-) create mode 100644 idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/ForEachTransformation.kt create mode 100644 idea/testData/intentions/loopToCallChain/firstOrNull_returnNotNullIfNone2.kt.after create mode 100644 idea/testData/intentions/loopToCallChain/forEach.kt create mode 100644 idea/testData/intentions/loopToCallChain/forEach.kt.after create mode 100644 idea/testData/intentions/loopToCallChain/forEach_notAvailable.kt create mode 100644 idea/testData/intentions/loopToCallChain/resultCollectionUsedInsideLoop.kt.after diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/interfaces.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/interfaces.kt index afde111c8fb..3aad5e3a1ec 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/interfaces.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/interfaces.kt @@ -101,7 +101,8 @@ data class MatchingState( * Matchers can assume that indexVariable is null if it's not used in the rest of the loop */ val indexVariable: KtCallableDeclaration?, - val initializationStatementsToDelete: Collection = emptyList() + val initializationStatementsToDelete: Collection = emptyList(), + val previousTransformations: List ) interface TransformationMatcher { diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/matchAndConvert.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/matchAndConvert.kt index bbc20569505..8e98f688d5f 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/matchAndConvert.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/matchAndConvert.kt @@ -25,6 +25,7 @@ import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade import org.jetbrains.kotlin.idea.intentions.loopToCallChain.result.AddToCollectionTransformation import org.jetbrains.kotlin.idea.intentions.loopToCallChain.result.CountTransformation import org.jetbrains.kotlin.idea.intentions.loopToCallChain.result.FindTransformationMatcher +import org.jetbrains.kotlin.idea.intentions.loopToCallChain.result.ForEachTransformation import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.FilterTransformation import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.FlatMapTransformation import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.IntroduceIndexMatcher @@ -52,7 +53,8 @@ object MatcherRegistrar { IntroduceIndexMatcher, FilterTransformation.Matcher, MapTransformation.Matcher, - FlatMapTransformation.Matcher + FlatMapTransformation.Matcher, + ForEachTransformation.Matcher ) } @@ -74,7 +76,8 @@ fun match(loop: KtForExpression): MatchResult? { innerLoop = loop, statements = listOf(loop.body ?: return null), inputVariable = inputVariable, - indexVariable = indexVariable + indexVariable = indexVariable, + previousTransformations = sequenceTransformations ) MatchLoop@ diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/ForEachTransformation.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/ForEachTransformation.kt new file mode 100644 index 00000000000..53d44301450 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/ForEachTransformation.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2010-2016 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 org.jetbrains.kotlin.idea.intentions.loopToCallChain.result + +import org.jetbrains.kotlin.idea.intentions.loopToCallChain.* +import org.jetbrains.kotlin.psi.KtCallableDeclaration +import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.psi.KtForExpression + +class ForEachTransformation( + loop: KtForExpression, + private val inputVariable: KtCallableDeclaration, + private val statement: KtExpression +) : ReplaceLoopResultTransformation(loop) { + + override val presentation: String + get() = "forEach{}" + + override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression { + val lambda = generateLambda(inputVariable, statement) + return chainedCallGenerator.generate("forEach $0:'{}'", lambda) + } + + /** + * Matches: + * for (...) { + * ... + * + * } + */ + object Matcher : TransformationMatcher { + override val indexVariableAllowed: Boolean + get() = false //TODO: support forEachIndexed + + override fun match(state: MatchingState): TransformationMatch.Result? { + if (state.previousTransformations.isEmpty()) return null // do not suggest conversion to just ".forEach{}" + + val statement = state.statements.singleOrNull() ?: return null + val transformation = ForEachTransformation(state.outerLoop, state.inputVariable, statement) + return TransformationMatch.Result(transformation) + } + } +} diff --git a/idea/testData/intentions/loopToCallChain/firstOrNull_returnNotNullIfNone2.kt b/idea/testData/intentions/loopToCallChain/firstOrNull_returnNotNullIfNone2.kt index ad0e7e72f0d..cf8b5ee6360 100644 --- a/idea/testData/intentions/loopToCallChain/firstOrNull_returnNotNullIfNone2.kt +++ b/idea/testData/intentions/loopToCallChain/firstOrNull_returnNotNullIfNone2.kt @@ -1,5 +1,4 @@ // WITH_RUNTIME -// IS_APPLICABLE: false fun foo(list: List): String? { for (s in list) { if (s == null || s.isNotEmpty()) { diff --git a/idea/testData/intentions/loopToCallChain/firstOrNull_returnNotNullIfNone2.kt.after b/idea/testData/intentions/loopToCallChain/firstOrNull_returnNotNullIfNone2.kt.after new file mode 100644 index 00000000000..70c33a66a30 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/firstOrNull_returnNotNullIfNone2.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME +fun foo(list: List): String? { + list + .filter { it == null || it.isNotEmpty() } + .forEach { return it } + return "" +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/forEach.kt b/idea/testData/intentions/loopToCallChain/forEach.kt new file mode 100644 index 00000000000..fe6c1a9e956 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/forEach.kt @@ -0,0 +1,9 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'filter{}.forEach{}'" +fun foo(list: List) { + for (s in list) { + if (s.isNotBlank()) { + println(s) + } + } +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/forEach.kt.after b/idea/testData/intentions/loopToCallChain/forEach.kt.after new file mode 100644 index 00000000000..be03fa3a074 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/forEach.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'filter{}.forEach{}'" +fun foo(list: List) { + list + .filter { it.isNotBlank() } + .forEach { println(it) } +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/forEach_notAvailable.kt b/idea/testData/intentions/loopToCallChain/forEach_notAvailable.kt new file mode 100644 index 00000000000..750a6ea3291 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/forEach_notAvailable.kt @@ -0,0 +1,7 @@ +// WITH_RUNTIME +// IS_APPLICABLE: false +fun foo(list: List) { + for (s in list) { + println(s) + } +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/resultCollectionUsedInsideLoop.kt b/idea/testData/intentions/loopToCallChain/resultCollectionUsedInsideLoop.kt index 192ededcd81..2545bbb4723 100644 --- a/idea/testData/intentions/loopToCallChain/resultCollectionUsedInsideLoop.kt +++ b/idea/testData/intentions/loopToCallChain/resultCollectionUsedInsideLoop.kt @@ -1,5 +1,4 @@ // WITH_RUNTIME -// IS_APPLICABLE: false import java.util.ArrayList fun foo(list: List): List { diff --git a/idea/testData/intentions/loopToCallChain/resultCollectionUsedInsideLoop.kt.after b/idea/testData/intentions/loopToCallChain/resultCollectionUsedInsideLoop.kt.after new file mode 100644 index 00000000000..8b23f09062c --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/resultCollectionUsedInsideLoop.kt.after @@ -0,0 +1,10 @@ +// WITH_RUNTIME +import java.util.ArrayList + +fun foo(list: List): List { + val result = ArrayList() + list + .filter { it.length > result.size } + .forEach { result.add(it.hashCode()) } + return result +} \ No newline at end of file