diff --git a/idea/resources/intentionDescriptions/LoopToCallChainIntention/after.kt.template b/idea/resources/intentionDescriptions/LoopToCallChainIntention/after.kt.template deleted file mode 100644 index f2941d61a08..00000000000 --- a/idea/resources/intentionDescriptions/LoopToCallChainIntention/after.kt.template +++ /dev/null @@ -1,3 +0,0 @@ -fun foo(list: List): String? { - return list.firstOrNull { it.length > 0 } -} \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/LoopToCallChainIntention/before.kt.template b/idea/resources/intentionDescriptions/LoopToCallChainIntention/before.kt.template deleted file mode 100644 index 46811f931d8..00000000000 --- a/idea/resources/intentionDescriptions/LoopToCallChainIntention/before.kt.template +++ /dev/null @@ -1,8 +0,0 @@ -fun foo(list: List): String? { - for (s in list) { - if (s.length > 0) { - return s - } - } - return null -} \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/LoopToCallChainIntention/description.html b/idea/resources/intentionDescriptions/LoopToCallChainIntention/description.html deleted file mode 100644 index bb27c3fd502..00000000000 --- a/idea/resources/intentionDescriptions/LoopToCallChainIntention/description.html +++ /dev/null @@ -1,5 +0,0 @@ - - -This intention converts a for-loop into a sequence of stdlib-operations (like "map", "filter" etc) - - diff --git a/idea/resources/intentionDescriptions/LoopToLazyCallChainIntention/after.kt.template b/idea/resources/intentionDescriptions/LoopToLazyCallChainIntention/after.kt.template deleted file mode 100644 index b007c74f7d4..00000000000 --- a/idea/resources/intentionDescriptions/LoopToLazyCallChainIntention/after.kt.template +++ /dev/null @@ -1,7 +0,0 @@ -fun foo(list: List) { - val sum = list - .asSequence() - .map { it.calcSomething() } - .filter { it > 0 } - .sumBy { it } -} \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/LoopToLazyCallChainIntention/before.kt.template b/idea/resources/intentionDescriptions/LoopToLazyCallChainIntention/before.kt.template deleted file mode 100644 index ac9a6724b7d..00000000000 --- a/idea/resources/intentionDescriptions/LoopToLazyCallChainIntention/before.kt.template +++ /dev/null @@ -1,9 +0,0 @@ -fun foo(list: List) { - val sum = 0 - for (s in list) { - val x = s.calcSomething() - if (x > 0) { - sum += x - } - } -} \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/LoopToLazyCallChainIntention/description.html b/idea/resources/intentionDescriptions/LoopToLazyCallChainIntention/description.html deleted file mode 100644 index d00f61d09f2..00000000000 --- a/idea/resources/intentionDescriptions/LoopToLazyCallChainIntention/description.html +++ /dev/null @@ -1,5 +0,0 @@ - - -This intention converts a for-loop into a sequence of stdlib-operations (like "map", "filter" etc) with lazy evaluation (using 'asSequence()' method) - - diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 09109693fb7..a985bb5dca4 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -1032,16 +1032,6 @@ Kotlin - - org.jetbrains.kotlin.idea.intentions.loopToCallChain.LoopToCallChainIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.loopToCallChain.LoopToLazyCallChainIntention - Kotlin - - org.jetbrains.kotlin.idea.intentions.loopToCallChain.UseWithIndexIntention Kotlin diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/LoopToCallChainInspection.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/LoopToCallChainInspection.kt new file mode 100644 index 00000000000..d6966ac43cb --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/LoopToCallChainInspection.kt @@ -0,0 +1,147 @@ +/* + * 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 + +import com.intellij.codeInspection.* +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.TextRange +import org.jetbrains.kotlin.idea.core.moveCaret +import org.jetbrains.kotlin.idea.inspections.AbstractKotlinInspection +import org.jetbrains.kotlin.idea.inspections.findExistingEditor +import org.jetbrains.kotlin.idea.intentions.SelfTargetingRangeIntention +import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.AsSequenceTransformation +import org.jetbrains.kotlin.psi.KtForExpression +import org.jetbrains.kotlin.psi.KtProperty +import org.jetbrains.kotlin.psi.KtVisitorVoid +import org.jetbrains.kotlin.psi.psiUtil.getParentOfType +import org.jetbrains.kotlin.psi.psiUtil.startOffset + +class LoopToCallChainInspection : AbstractKotlinInspection() { + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession) = + object : KtVisitorVoid() { + val nonLazyIntention = LoopToCallChainIntention() + val lazyIntention = LoopToLazyCallChainIntention() + + override fun visitForExpression(expression: KtForExpression) { + super.visitForExpression(expression) + + val nonLazyApplicable = nonLazyIntention.applicabilityRange(expression) != null + val lazyApplicable = lazyIntention.applicabilityRange(expression) != null + + if (!nonLazyApplicable && !lazyApplicable) return + + val fixes = mutableListOf() + if (nonLazyApplicable) { + fixes += Fix(lazy = false, text = nonLazyIntention.text) + } + if (lazyApplicable) { + fixes += Fix(lazy = true, text = lazyIntention.text) + } + + holder.registerProblem( + expression.forKeyword, + "Loop can be replaced with stdlib operations", + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + *fixes.toTypedArray() + ) + } + } + + class Fix(val lazy: Boolean, val text: String = "") : LocalQuickFix { + override fun getFamilyName(): String { + return if (lazy) { + "Replace with stdlib operations with use of 'asSequence()'" + } + else { + "Replace with stdlib operations" + } + } + + override fun getName() = text + + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + val expression = descriptor.psiElement.getParentOfType(strict = true) ?: return + applyFix(expression) + } + + fun applyFix(expression: KtForExpression, editor: Editor? = expression.findExistingEditor()) { + val match = match(expression, lazy, true) ?: return + val result = convertLoop(expression, match) + + val offset = when (result) { + // if result is variable declaration, put the caret onto its name to allow quick inline + is KtProperty -> result.nameIdentifier?.startOffset ?: result.startOffset + else -> result.startOffset + } + + editor?.moveCaret(offset) + } + } +} + +class LoopToCallChainIntention : AbstractLoopToCallChainIntention( + lazy = false, + text = "Replace with stdlib operations" +) + +class LoopToLazyCallChainIntention : AbstractLoopToCallChainIntention( + lazy = true, + text = "Replace with stdlib operations with use of 'asSequence()'" +) + +abstract class AbstractLoopToCallChainIntention( + private val lazy: Boolean, + text: String +) : SelfTargetingRangeIntention( + KtForExpression::class.java, + text +) { + override fun applicabilityRange(element: KtForExpression): TextRange? { + val match = match(element, lazy, false) + text = if (match != null) "Replace with '${match.transformationMatch.buildPresentation()}'" else defaultText + return if (match != null) element.forKeyword.textRange else null + } + + private fun TransformationMatch.Result.buildPresentation(): String { + return buildPresentation(sequenceTransformations + resultTransformation, null) + } + + private fun buildPresentation(transformations: List, prevPresentation: String?): String { + if (transformations.size > MAX) { + if (transformations[0] is AsSequenceTransformation) { + return buildPresentation(transformations.drop(1), transformations[0].presentation) + } + + return buildPresentation(transformations.drop(transformations.size - MAX), prevPresentation?.let { it + ".." } ?: "..") + } + + var result: String? = prevPresentation + for (transformation in transformations) { + result = transformation.buildPresentation(result) + } + return result!! + } + + override fun applyTo(element: KtForExpression, editor: Editor?) { + LoopToCallChainInspection.Fix(lazy).applyFix(element, editor) + } + + companion object { + const val MAX = 3 + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/LoopToCallChainIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/LoopToCallChainIntention.kt deleted file mode 100644 index e00838e8ccd..00000000000 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/LoopToCallChainIntention.kt +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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 - -import com.intellij.codeInsight.intention.LowPriorityAction -import com.intellij.openapi.editor.Editor -import com.intellij.openapi.util.TextRange -import org.jetbrains.kotlin.idea.core.moveCaret -import org.jetbrains.kotlin.idea.inspections.IntentionBasedInspection -import org.jetbrains.kotlin.idea.intentions.SelfTargetingRangeIntention -import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.AsSequenceTransformation -import org.jetbrains.kotlin.psi.KtForExpression -import org.jetbrains.kotlin.psi.KtProperty -import org.jetbrains.kotlin.psi.psiUtil.startOffset - -class LoopToCallChainInspection : IntentionBasedInspection( - listOf(IntentionData(LoopToCallChainIntention::class), IntentionData(LoopToLazyCallChainIntention::class)), - problemText = "Loop can be replaced with stdlib operations" -) - -class LoopToCallChainIntention : AbstractLoopToCallChainIntention(lazy = false, text = "Replace with stdlib operations") - -class LoopToLazyCallChainIntention : AbstractLoopToCallChainIntention(lazy = true, text = "Replace with stdlib operations with use of 'asSequence()'"), LowPriorityAction - -abstract class AbstractLoopToCallChainIntention(private val lazy: Boolean, text: String) : SelfTargetingRangeIntention( - KtForExpression::class.java, - text -) { - override fun applicabilityRange(element: KtForExpression): TextRange? { - val match = match(element, lazy, false) - text = if (match != null) "Replace with '${match.transformationMatch.buildPresentation()}'" else defaultText - return if (match != null) element.forKeyword.textRange else null - } - - private fun TransformationMatch.Result.buildPresentation(): String { - return buildPresentation(sequenceTransformations + resultTransformation, null) - } - - private fun buildPresentation(transformations: List, prevPresentation: String?): String { - val MAX = 3 - if (transformations.size > MAX) { - if (transformations[0] is AsSequenceTransformation) { - return buildPresentation(transformations.drop(1), transformations[0].presentation) - } - - return buildPresentation(transformations.drop(transformations.size - MAX), prevPresentation?.let { it + ".." } ?: "..") - } - - var result: String? = prevPresentation - for (transformation in transformations) { - result = transformation.buildPresentation(result) - } - return result!! - } - - override fun applyTo(element: KtForExpression, editor: Editor?) { - val match = match(element, lazy, true)!! - val result = convertLoop(element, match) - - val offset = when (result) { - // if result is variable declaration, put the caret onto its name to allow quick inline - is KtProperty -> result.nameIdentifier?.startOffset ?: result.startOffset - else -> result.startOffset - } - - editor?.moveCaret(offset) - } -}