From db53794663242027003ecdfb23a7fdc6ca13271e Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Wed, 20 Apr 2016 16:34:49 +0300 Subject: [PATCH] Supported "count()" --- .../loopToCallChain/matchAndConvert.kt | 4 +- .../result/CountTransformation.kt | 88 +++++++++++++++++++ .../intentions/loopToCallChain/count1.kt | 11 +++ .../loopToCallChain/count1.kt.after | 6 ++ .../intentions/loopToCallChain/count2.kt | 9 ++ .../loopToCallChain/count2.kt.after | 6 ++ .../intentions/loopToCallChain/count_Long.kt | 11 +++ .../count_nonConstantInitial.kt | 13 +++ .../count_nonConstantInitial.kt.after | 8 ++ .../loopToCallChain/count_nonZeroInitial.kt | 11 +++ .../count_nonZeroInitial.kt.after | 6 ++ .../loopToCallChain/count_prefix.kt | 11 +++ .../loopToCallChain/count_prefix.kt.after | 6 ++ .../count_variableUsedBefore.kt | 11 +++ 14 files changed, 200 insertions(+), 1 deletion(-) create mode 100644 idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/CountTransformation.kt create mode 100644 idea/testData/intentions/loopToCallChain/count1.kt create mode 100644 idea/testData/intentions/loopToCallChain/count1.kt.after create mode 100644 idea/testData/intentions/loopToCallChain/count2.kt create mode 100644 idea/testData/intentions/loopToCallChain/count2.kt.after create mode 100644 idea/testData/intentions/loopToCallChain/count_Long.kt create mode 100644 idea/testData/intentions/loopToCallChain/count_nonConstantInitial.kt create mode 100644 idea/testData/intentions/loopToCallChain/count_nonConstantInitial.kt.after create mode 100644 idea/testData/intentions/loopToCallChain/count_nonZeroInitial.kt create mode 100644 idea/testData/intentions/loopToCallChain/count_nonZeroInitial.kt.after create mode 100644 idea/testData/intentions/loopToCallChain/count_prefix.kt create mode 100644 idea/testData/intentions/loopToCallChain/count_prefix.kt.after create mode 100644 idea/testData/intentions/loopToCallChain/count_variableUsedBefore.kt 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 07ce921e72f..a8763ed19e1 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/matchAndConvert.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/matchAndConvert.kt @@ -21,6 +21,7 @@ import org.jetbrains.kotlin.idea.analysis.analyzeInContext import org.jetbrains.kotlin.idea.caches.resolve.analyze 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.FindAndAssignTransformation import org.jetbrains.kotlin.idea.intentions.loopToCallChain.result.FindAndReturnTransformation import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.FilterTransformation @@ -48,7 +49,8 @@ object MatcherRegistrar { val resultMatchers: Collection = listOf( FindAndReturnTransformation.Matcher, FindAndAssignTransformation.Matcher, - AddToCollectionTransformation.Matcher + AddToCollectionTransformation.Matcher, + CountTransformation.Matcher ) } diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/CountTransformation.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/CountTransformation.kt new file mode 100644 index 00000000000..27b2d015775 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/CountTransformation.kt @@ -0,0 +1,88 @@ +/* + * 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 com.intellij.psi.search.LocalSearchScope +import com.intellij.psi.search.searches.ReferencesSearch +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.descriptors.VariableDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny +import org.jetbrains.kotlin.idea.intentions.loopToCallChain.* +import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.FilterTransformation +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.* + +class CountTransformation( + loop: KtForExpression, + inputVariable: KtCallableDeclaration, + initialization: VariableInitialization, + private val filter: KtExpression? +) : AssignToVariableResultTransformation(loop, inputVariable, initialization) { + + override fun mergeWithPrevious(previousTransformation: SequenceTransformation): ResultTransformation? { + if (previousTransformation !is FilterTransformation) return null + assert(filter == null) { "Should not happen because no 2 consecutive FilterTransformation's possible"} + return CountTransformation(loop, previousTransformation.inputVariable, initialization, previousTransformation.effectiveCondition()) + } + + override val presentation: String + get() = "count" + (if (filter != null) "{}" else "()") + + override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression { + val call = if (filter != null) { + val lambda = generateLambda(inputVariable, filter) + chainedCallGenerator.generate("count $0:'{}'", lambda) + } + else { + chainedCallGenerator.generate("count()") + } + + if ((initialization.initializer as? KtConstantExpression)?.text == "0") { + return call + } + else { + return KtPsiFactory(call).createExpressionByPattern("$0 + $1", initialization.initializer, call) + } + } + + /** + * Matches: + * val variable = 0 + * for (...) { + * ... + * variable++ (or ++variable) + * } + */ + object Matcher : ResultTransformationMatcher { + override fun match(state: MatchingState): ResultTransformationMatch? { + val statement = state.statements.singleOrNull() as? KtUnaryExpression ?: return null + if (statement.operationToken != KtTokens.PLUSPLUS) return null + + val operand = statement.baseExpression + val initialization = operand?.detectInitializationBeforeLoop(state.outerLoop) ?: return null + + val usageCountInLoop = ReferencesSearch.search(initialization.variable, LocalSearchScope(state.outerLoop)).count() + if (usageCountInLoop != 1) return null // this should be the only usage of this variable inside the loop + + val variableType = (initialization.variable.resolveToDescriptorIfAny() as? VariableDescriptor)?.type ?: return null + if (!KotlinBuiltIns.isInt(variableType)) return null + + val transformation = CountTransformation(state.outerLoop, state.inputVariable, initialization, null) + return ResultTransformationMatch(transformation) + } + } +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/count1.kt b/idea/testData/intentions/loopToCallChain/count1.kt new file mode 100644 index 00000000000..0807bf1d14a --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/count1.kt @@ -0,0 +1,11 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'count{}'" +fun foo(list: List): Int { + var count = 0 + for (s in list) { + if (s.isNotBlank()) { + count++ + } + } + return count +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/count1.kt.after b/idea/testData/intentions/loopToCallChain/count1.kt.after new file mode 100644 index 00000000000..c6a15f3c87e --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/count1.kt.after @@ -0,0 +1,6 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'count{}'" +fun foo(list: List): Int { + val count = list.count { it.isNotBlank() } + return count +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/count2.kt b/idea/testData/intentions/loopToCallChain/count2.kt new file mode 100644 index 00000000000..25629baf997 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/count2.kt @@ -0,0 +1,9 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'count()'" +fun foo(list: Iterable): Int { + var count = 0 + for (s in list) { + count++ + } + return count +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/count2.kt.after b/idea/testData/intentions/loopToCallChain/count2.kt.after new file mode 100644 index 00000000000..f8696726121 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/count2.kt.after @@ -0,0 +1,6 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'count()'" +fun foo(list: Iterable): Int { + val count = list.count() + return count +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/count_Long.kt b/idea/testData/intentions/loopToCallChain/count_Long.kt new file mode 100644 index 00000000000..a8575b565ff --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/count_Long.kt @@ -0,0 +1,11 @@ +// WITH_RUNTIME +// IS_APPLICABLE: false +fun foo(list: List): Long { + var count = 0L + for (s in list) { + if (s.length > 10) { + count++ + } + } + return count +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/count_nonConstantInitial.kt b/idea/testData/intentions/loopToCallChain/count_nonConstantInitial.kt new file mode 100644 index 00000000000..a42ea1e237f --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/count_nonConstantInitial.kt @@ -0,0 +1,13 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'count{}'" +fun foo(list: List): Int { + var count = bar() + for (s in list) { + if (s.isNotBlank()) { + count++ + } + } + return count +} + +fun bar(): Int = 0 \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/count_nonConstantInitial.kt.after b/idea/testData/intentions/loopToCallChain/count_nonConstantInitial.kt.after new file mode 100644 index 00000000000..aee44505484 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/count_nonConstantInitial.kt.after @@ -0,0 +1,8 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'count{}'" +fun foo(list: List): Int { + val count = bar() + list.count { it.isNotBlank() } + return count +} + +fun bar(): Int = 0 \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/count_nonZeroInitial.kt b/idea/testData/intentions/loopToCallChain/count_nonZeroInitial.kt new file mode 100644 index 00000000000..488b69d0b5b --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/count_nonZeroInitial.kt @@ -0,0 +1,11 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'count{}'" +fun foo(list: List): Int { + var count = 1 + for (s in list) { + if (s.isNotBlank()) { + count++ + } + } + return count +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/count_nonZeroInitial.kt.after b/idea/testData/intentions/loopToCallChain/count_nonZeroInitial.kt.after new file mode 100644 index 00000000000..b17838f8228 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/count_nonZeroInitial.kt.after @@ -0,0 +1,6 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'count{}'" +fun foo(list: List): Int { + val count = 1 + list.count { it.isNotBlank() } + return count +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/count_prefix.kt b/idea/testData/intentions/loopToCallChain/count_prefix.kt new file mode 100644 index 00000000000..e9fd0601de9 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/count_prefix.kt @@ -0,0 +1,11 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'count{}'" +fun foo(list: List): Int { + var count = 0 + for (s in list) { + if (s.isNotBlank()) { + ++count + } + } + return count +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/count_prefix.kt.after b/idea/testData/intentions/loopToCallChain/count_prefix.kt.after new file mode 100644 index 00000000000..c6a15f3c87e --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/count_prefix.kt.after @@ -0,0 +1,6 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'count{}'" +fun foo(list: List): Int { + val count = list.count { it.isNotBlank() } + return count +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/count_variableUsedBefore.kt b/idea/testData/intentions/loopToCallChain/count_variableUsedBefore.kt new file mode 100644 index 00000000000..fd786da2407 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/count_variableUsedBefore.kt @@ -0,0 +1,11 @@ +// WITH_RUNTIME +// IS_APPLICABLE: false +fun foo(list: List): Int { + var count = 0 + for (s in list) { + if (s.length > count) { + count++ + } + } + return count +} \ No newline at end of file