Refactoring: make "loop to call chain" AbstractKotlinInspection
This commit is contained in:
@@ -1,3 +0,0 @@
|
|||||||
fun foo(list: List<String>): String? {
|
|
||||||
<spot>return list.firstOrNull { it.length > 0 }</spot>
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
fun foo(list: List<String>): String? {
|
|
||||||
<spot>for (s in list) {
|
|
||||||
if (s.length > 0) {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null</spot>
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<html>
|
|
||||||
<body>
|
|
||||||
This intention converts a for-loop into a sequence of stdlib-operations (like "map", "filter" etc)
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
fun foo(list: List<String>) {
|
|
||||||
val sum = <spot>list
|
|
||||||
.asSequence()
|
|
||||||
.map { it.calcSomething() }
|
|
||||||
.filter { it > 0 }
|
|
||||||
.sumBy { it }</spot>
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
fun foo(list: List<String>) {
|
|
||||||
val sum = 0
|
|
||||||
<spot>for (s in list) {
|
|
||||||
val x = s.calcSomething()
|
|
||||||
if (x > 0) {
|
|
||||||
sum += x
|
|
||||||
}
|
|
||||||
}</spot>
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<html>
|
|
||||||
<body>
|
|
||||||
This intention converts a for-loop into a sequence of stdlib-operations (like "map", "filter" etc) with lazy evaluation (using 'asSequence()' method)
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1032,16 +1032,6 @@
|
|||||||
<category>Kotlin</category>
|
<category>Kotlin</category>
|
||||||
</intentionAction>
|
</intentionAction>
|
||||||
|
|
||||||
<intentionAction>
|
|
||||||
<className>org.jetbrains.kotlin.idea.intentions.loopToCallChain.LoopToCallChainIntention</className>
|
|
||||||
<category>Kotlin</category>
|
|
||||||
</intentionAction>
|
|
||||||
|
|
||||||
<intentionAction>
|
|
||||||
<className>org.jetbrains.kotlin.idea.intentions.loopToCallChain.LoopToLazyCallChainIntention</className>
|
|
||||||
<category>Kotlin</category>
|
|
||||||
</intentionAction>
|
|
||||||
|
|
||||||
<intentionAction>
|
<intentionAction>
|
||||||
<className>org.jetbrains.kotlin.idea.intentions.loopToCallChain.UseWithIndexIntention</className>
|
<className>org.jetbrains.kotlin.idea.intentions.loopToCallChain.UseWithIndexIntention</className>
|
||||||
<category>Kotlin</category>
|
<category>Kotlin</category>
|
||||||
|
|||||||
+147
@@ -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<Fix>()
|
||||||
|
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<KtForExpression>(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>(
|
||||||
|
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<Transformation>, 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
|
||||||
|
}
|
||||||
|
}
|
||||||
-82
@@ -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<KtForExpression>(
|
|
||||||
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>(
|
|
||||||
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<Transformation>, 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user