Supported indexOfFirst/indexOfLast
This commit is contained in:
+1
-1
@@ -39,7 +39,7 @@ class LoopToCallChainIntention : SelfTargetingRangeIntention<KtForExpression>(
|
||||
return if (match != null) element.forKeyword.textRange else null
|
||||
}
|
||||
|
||||
private fun ResultTransformationMatch.buildPresentation(): String {
|
||||
private fun TransformationMatch.Result.buildPresentation(): String {
|
||||
var transformations = sequenceTransformations + resultTransformation
|
||||
val MAX = 3
|
||||
var result: String? = null
|
||||
|
||||
@@ -57,6 +57,7 @@ fun KtExpression?.isSimpleName(name: Name): Boolean {
|
||||
}
|
||||
|
||||
fun KtCallableDeclaration.hasUsages(inElement: KtElement): Boolean {
|
||||
assert(inElement.isPhysical)
|
||||
return hasUsages(listOf(inElement))
|
||||
}
|
||||
|
||||
|
||||
+116
-77
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.idea.intentions.loopToCallChain
|
||||
import org.jetbrains.kotlin.descriptors.VariableDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
||||
import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isNullExpression
|
||||
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.FilterTransformation
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.types.typeUtil.TypeNullability
|
||||
import org.jetbrains.kotlin.types.typeUtil.nullability
|
||||
@@ -26,9 +27,14 @@ import org.jetbrains.kotlin.types.typeUtil.nullability
|
||||
interface FindOperatorGenerator {
|
||||
val functionName: String
|
||||
|
||||
val hasFilter: Boolean
|
||||
|
||||
val presentation: String
|
||||
get() = functionName + (if (hasFilter) "{}" else "()")
|
||||
|
||||
val shouldUseInputVariable: Boolean
|
||||
|
||||
fun generate(chainedCallGenerator: ChainedCallGenerator, filter: KtExpression?): KtExpression
|
||||
fun generate(chainedCallGenerator: ChainedCallGenerator): KtExpression
|
||||
|
||||
val chainCallCount: Int
|
||||
get() = 1
|
||||
@@ -38,11 +44,15 @@ fun buildFindOperationGenerator(
|
||||
valueIfFound: KtExpression,
|
||||
valueIfNotFound: KtExpression,
|
||||
inputVariable: KtCallableDeclaration,
|
||||
indexVariable: KtCallableDeclaration?,
|
||||
filterTransformation: FilterTransformation?,
|
||||
findFirst: Boolean
|
||||
): FindOperatorGenerator? {
|
||||
assert(valueIfFound.isPhysical)
|
||||
assert(valueIfNotFound.isPhysical)
|
||||
|
||||
val filterCondition = filterTransformation?.effectiveCondition()
|
||||
|
||||
fun generateChainedCall(stdlibFunName: String, chainedCallGenerator: ChainedCallGenerator, filter: KtExpression?): KtExpression {
|
||||
return if (filter == null) {
|
||||
chainedCallGenerator.generate("$stdlibFunName()")
|
||||
@@ -53,97 +63,126 @@ fun buildFindOperationGenerator(
|
||||
}
|
||||
}
|
||||
|
||||
class SimpleGenerator(override val functionName: String, override val shouldUseInputVariable: Boolean) : FindOperatorGenerator {
|
||||
override fun generate(chainedCallGenerator: ChainedCallGenerator, filter: KtExpression?): KtExpression {
|
||||
return generateChainedCall(functionName, chainedCallGenerator, filter)
|
||||
class SimpleGenerator(
|
||||
override val functionName: String,
|
||||
override val shouldUseInputVariable: Boolean
|
||||
) : FindOperatorGenerator {
|
||||
override val hasFilter: Boolean
|
||||
get() = filterCondition != null
|
||||
|
||||
override fun generate(chainedCallGenerator: ChainedCallGenerator): KtExpression {
|
||||
return generateChainedCall(functionName, chainedCallGenerator, filterCondition)
|
||||
}
|
||||
}
|
||||
|
||||
val inputVariableCanHoldNull = (inputVariable.resolveToDescriptor() as VariableDescriptor).type.nullability() != TypeNullability.NOT_NULL
|
||||
if (indexVariable != null) {
|
||||
if (filterTransformation == null) return null // makes no sense, indexVariable must be always 0
|
||||
if (filterTransformation.indexVariable != null) return null // cannot use index in condition for indexOfFirst/indexOfLast
|
||||
|
||||
fun FindOperatorGenerator.useElvisOperatorIfNeeded(): FindOperatorGenerator? {
|
||||
if (valueIfNotFound.isNullExpression()) return this
|
||||
|
||||
// we cannot use ?: if found value can be null
|
||||
if (inputVariableCanHoldNull) return null
|
||||
|
||||
return object: FindOperatorGenerator by this {
|
||||
override fun generate(chainedCallGenerator: ChainedCallGenerator, filter: KtExpression?): KtExpression {
|
||||
val generated = this@useElvisOperatorIfNeeded.generate(chainedCallGenerator, filter)
|
||||
return KtPsiFactory(generated).createExpressionByPattern("$0 ?: $1", generated, valueIfNotFound)
|
||||
}
|
||||
//TODO: what if value when not found is not "-1"?
|
||||
if (valueIfFound.isVariableReference(indexVariable) && valueIfNotFound.text == "-1") {
|
||||
return SimpleGenerator(if (findFirst) "indexOfFirst" else "indexOfLast", shouldUseInputVariable = false)
|
||||
}
|
||||
|
||||
return null
|
||||
|
||||
}
|
||||
else {
|
||||
val inputVariableCanHoldNull = (inputVariable.resolveToDescriptor() as VariableDescriptor).type.nullability() != TypeNullability.NOT_NULL
|
||||
|
||||
when {
|
||||
valueIfFound.isVariableReference(inputVariable) -> {
|
||||
val generator = SimpleGenerator(if (findFirst) "firstOrNull" else "lastOrNull", shouldUseInputVariable = true)
|
||||
return generator.useElvisOperatorIfNeeded()
|
||||
}
|
||||
fun FindOperatorGenerator.useElvisOperatorIfNeeded(): FindOperatorGenerator? {
|
||||
if (valueIfNotFound.isNullExpression()) return this
|
||||
|
||||
valueIfFound.isTrueConstant() && valueIfNotFound.isFalseConstant() -> return SimpleGenerator("any", shouldUseInputVariable = false)
|
||||
|
||||
valueIfFound.isFalseConstant() && valueIfNotFound.isTrueConstant() -> return SimpleGenerator("none", shouldUseInputVariable = false)
|
||||
|
||||
inputVariable.hasUsages(valueIfFound) -> {
|
||||
if (!findFirst) return null // too dangerous because of side effects
|
||||
|
||||
// specially handle the case when the result expression is "<input variable>.<some call>" or "<input variable>?.<some call>"
|
||||
val qualifiedExpression = valueIfFound as? KtQualifiedExpression
|
||||
if (qualifiedExpression != null) {
|
||||
val receiver = qualifiedExpression.receiverExpression
|
||||
val selector = qualifiedExpression.selectorExpression
|
||||
if (receiver.isVariableReference(inputVariable) && selector != null && !inputVariable.hasUsages(selector)) {
|
||||
return object: FindOperatorGenerator {
|
||||
override val functionName: String
|
||||
get() = "firstOrNull"
|
||||
|
||||
override val shouldUseInputVariable: Boolean
|
||||
get() = true
|
||||
|
||||
override val chainCallCount: Int
|
||||
get() = 2
|
||||
|
||||
override fun generate(chainedCallGenerator: ChainedCallGenerator, filter: KtExpression?): KtExpression {
|
||||
val findFirstCall = generateChainedCall(functionName, chainedCallGenerator, filter)
|
||||
return chainedCallGenerator.generate("$0", selector, receiver = findFirstCall, safeCall = true)
|
||||
}
|
||||
}.useElvisOperatorIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
// in case of nullable input variable we cannot distinguish by the result of "firstOrNull" whether nothing was found or 'null' was found
|
||||
// we cannot use ?: if found value can be null
|
||||
if (inputVariableCanHoldNull) return null
|
||||
|
||||
return object: FindOperatorGenerator {
|
||||
override val functionName: String
|
||||
get() = "firstOrNull"
|
||||
|
||||
override val shouldUseInputVariable: Boolean
|
||||
get() = true
|
||||
|
||||
override val chainCallCount: Int
|
||||
get() = 2 // also includes "let"
|
||||
|
||||
override fun generate(chainedCallGenerator: ChainedCallGenerator, filter: KtExpression?): KtExpression {
|
||||
val findFirstCall = generateChainedCall(functionName, chainedCallGenerator, filter)
|
||||
val letBody = generateLambda(inputVariable, valueIfFound)
|
||||
return chainedCallGenerator.generate("let $0:'{}'", letBody, receiver = findFirstCall, safeCall = true)
|
||||
return object: FindOperatorGenerator by this {
|
||||
override fun generate(chainedCallGenerator: ChainedCallGenerator): KtExpression {
|
||||
val generated = this@useElvisOperatorIfNeeded.generate(chainedCallGenerator)
|
||||
return KtPsiFactory(generated).createExpressionByPattern("$0 ?: $1", generated, valueIfNotFound)
|
||||
}
|
||||
}.useElvisOperatorIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
return object: FindOperatorGenerator {
|
||||
override val functionName: String
|
||||
get() = "any"
|
||||
when {
|
||||
valueIfFound.isVariableReference(inputVariable) -> {
|
||||
val generator = SimpleGenerator(if (findFirst) "firstOrNull" else "lastOrNull", shouldUseInputVariable = true)
|
||||
return generator.useElvisOperatorIfNeeded()
|
||||
}
|
||||
|
||||
override val shouldUseInputVariable: Boolean
|
||||
get() = false
|
||||
valueIfFound.isTrueConstant() && valueIfNotFound.isFalseConstant() -> return SimpleGenerator("any", shouldUseInputVariable = false)
|
||||
|
||||
override fun generate(chainedCallGenerator: ChainedCallGenerator, filter: KtExpression?): KtExpression {
|
||||
val chainedCall = generateChainedCall(functionName, chainedCallGenerator, filter)
|
||||
return KtPsiFactory(chainedCall).createExpressionByPattern("if ($0) $1 else $2", chainedCall, valueIfFound, valueIfNotFound)
|
||||
valueIfFound.isFalseConstant() && valueIfNotFound.isTrueConstant() -> return SimpleGenerator("none", shouldUseInputVariable = false)
|
||||
|
||||
inputVariable.hasUsages(valueIfFound) -> {
|
||||
if (!findFirst) return null // too dangerous because of side effects
|
||||
|
||||
// specially handle the case when the result expression is "<input variable>.<some call>" or "<input variable>?.<some call>"
|
||||
val qualifiedExpression = valueIfFound as? KtQualifiedExpression
|
||||
if (qualifiedExpression != null) {
|
||||
val receiver = qualifiedExpression.receiverExpression
|
||||
val selector = qualifiedExpression.selectorExpression
|
||||
if (receiver.isVariableReference(inputVariable) && selector != null && !inputVariable.hasUsages(selector)) {
|
||||
return object: FindOperatorGenerator {
|
||||
override val functionName: String
|
||||
get() = "firstOrNull"
|
||||
|
||||
override val hasFilter: Boolean
|
||||
get() = filterCondition != null
|
||||
|
||||
override val shouldUseInputVariable: Boolean
|
||||
get() = true
|
||||
|
||||
override val chainCallCount: Int
|
||||
get() = 2
|
||||
|
||||
override fun generate(chainedCallGenerator: ChainedCallGenerator): KtExpression {
|
||||
val findFirstCall = generateChainedCall(functionName, chainedCallGenerator, filterCondition)
|
||||
return chainedCallGenerator.generate("$0", selector, receiver = findFirstCall, safeCall = true)
|
||||
}
|
||||
}.useElvisOperatorIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
// in case of nullable input variable we cannot distinguish by the result of "firstOrNull" whether nothing was found or 'null' was found
|
||||
if (inputVariableCanHoldNull) return null
|
||||
|
||||
return object: FindOperatorGenerator {
|
||||
override val functionName: String
|
||||
get() = "firstOrNull"
|
||||
|
||||
override val hasFilter: Boolean
|
||||
get() = filterCondition != null
|
||||
|
||||
override val shouldUseInputVariable: Boolean
|
||||
get() = true
|
||||
|
||||
override val chainCallCount: Int
|
||||
get() = 2 // also includes "let"
|
||||
|
||||
override fun generate(chainedCallGenerator: ChainedCallGenerator): KtExpression {
|
||||
val findFirstCall = generateChainedCall(functionName, chainedCallGenerator, filterCondition)
|
||||
val letBody = generateLambda(inputVariable, valueIfFound)
|
||||
return chainedCallGenerator.generate("let $0:'{}'", letBody, receiver = findFirstCall, safeCall = true)
|
||||
}
|
||||
}.useElvisOperatorIfNeeded()
|
||||
}
|
||||
|
||||
else -> {
|
||||
return object: FindOperatorGenerator {
|
||||
override val functionName: String
|
||||
get() = "any"
|
||||
|
||||
override val hasFilter: Boolean
|
||||
get() = filterCondition != null
|
||||
|
||||
override val shouldUseInputVariable: Boolean
|
||||
get() = false
|
||||
|
||||
override fun generate(chainedCallGenerator: ChainedCallGenerator): KtExpression {
|
||||
val chainedCall = generateChainedCall(functionName, chainedCallGenerator, filterCondition)
|
||||
return KtPsiFactory(chainedCall).createExpressionByPattern("if ($0) $1 else $2", chainedCall, valueIfFound, valueIfNotFound)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +100,11 @@ data class MatchingState(
|
||||
val initializationStatementsToDelete: Collection<KtExpression> = emptyList()
|
||||
)
|
||||
|
||||
interface BaseMatcher {
|
||||
interface TransformationMatcher {
|
||||
fun match(state: MatchingState): TransformationMatch?
|
||||
|
||||
val indexVariableAllowed: Boolean
|
||||
|
||||
/**
|
||||
* Implementors should return true if they match some constructs with expression-embedded break or continue.
|
||||
* In this case they are obliged to deal with them and filter out invalid cases.
|
||||
@@ -109,36 +113,28 @@ interface BaseMatcher {
|
||||
get() = false
|
||||
}
|
||||
|
||||
/**
|
||||
* A matcher that can recognize one or more [SequenceTransformation]'s
|
||||
*/
|
||||
interface SequenceTransformationMatcher : BaseMatcher {
|
||||
fun match(state: MatchingState): SequenceTransformationMatch?
|
||||
}
|
||||
sealed class TransformationMatch(val sequenceTransformations: List<SequenceTransformation>) {
|
||||
abstract val allTransformations: List<Transformation>
|
||||
|
||||
class SequenceTransformationMatch(
|
||||
val transformations: List<SequenceTransformation>,
|
||||
val newState: MatchingState
|
||||
) {
|
||||
constructor(transformation: SequenceTransformation, newState: MatchingState) : this(listOf(transformation), newState)
|
||||
}
|
||||
/**
|
||||
* A partial match, includes [newState] for further matching
|
||||
*/
|
||||
class Sequence(transformations: List<SequenceTransformation>, val newState: MatchingState) : TransformationMatch(transformations) {
|
||||
constructor(transformation: SequenceTransformation, newState: MatchingState) : this(listOf(transformation), newState)
|
||||
|
||||
/**
|
||||
* A matcher that can recognize a [ResultTransformation] (optionally prepended by some [SequenceTransformation]'s).
|
||||
* Should match the whole rest part of the loop.
|
||||
*/
|
||||
interface ResultTransformationMatcher : BaseMatcher {
|
||||
fun match(state: MatchingState): ResultTransformationMatch?
|
||||
override val allTransformations: List<Transformation>
|
||||
get() = sequenceTransformations
|
||||
}
|
||||
|
||||
val indexVariableUsePossible: Boolean
|
||||
}
|
||||
/**
|
||||
* A match of the whole rest part of the loop
|
||||
*/
|
||||
class Result(val resultTransformation: ResultTransformation, sequenceTransformations: List<SequenceTransformation>) : TransformationMatch(sequenceTransformations) {
|
||||
constructor(resultTransformation: ResultTransformation, vararg sequenceTransformations: SequenceTransformation)
|
||||
: this(resultTransformation, sequenceTransformations.asList())
|
||||
|
||||
class ResultTransformationMatch(
|
||||
val resultTransformation: ResultTransformation,
|
||||
val sequenceTransformations: List<SequenceTransformation>
|
||||
) {
|
||||
constructor(resultTransformation: ResultTransformation, vararg sequenceTransformations: SequenceTransformation)
|
||||
: this(resultTransformation, sequenceTransformations.asList())
|
||||
override val allTransformations = sequenceTransformations + resultTransformation
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,8 +22,7 @@ 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.result.FindTransformationMatcher
|
||||
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
|
||||
@@ -42,24 +41,20 @@ import org.jetbrains.kotlin.utils.addToStdlib.check
|
||||
import java.util.*
|
||||
|
||||
object MatcherRegistrar {
|
||||
val sequenceMatchers: Collection<SequenceTransformationMatcher> = listOf(
|
||||
val matchers: Collection<TransformationMatcher> = listOf(
|
||||
FindTransformationMatcher,
|
||||
AddToCollectionTransformation.Matcher,
|
||||
CountTransformation.Matcher,
|
||||
IntroduceIndexMatcher,
|
||||
FilterTransformation.Matcher,
|
||||
MapTransformation.Matcher,
|
||||
FlatMapTransformation.Matcher
|
||||
)
|
||||
|
||||
val resultMatchers: Collection<ResultTransformationMatcher> = listOf(
|
||||
FindAndReturnTransformation.Matcher,
|
||||
FindAndAssignTransformation.Matcher,
|
||||
AddToCollectionTransformation.Matcher,
|
||||
CountTransformation.Matcher
|
||||
)
|
||||
}
|
||||
|
||||
data class MatchResult(
|
||||
val sequenceExpression: KtExpression,
|
||||
val transformationMatch: ResultTransformationMatch,
|
||||
val transformationMatch: TransformationMatch.Result,
|
||||
val initializationStatementsToDelete: Collection<KtExpression>
|
||||
)
|
||||
|
||||
@@ -91,52 +86,47 @@ fun match(loop: KtForExpression): MatchResult? {
|
||||
|
||||
val restContainsEmbeddedBreakOrContinue = loopContainsEmbeddedBreakOrContinue && state.statements.any { it.containsEmbeddedBreakOrContinue() }
|
||||
|
||||
for (matcher in MatcherRegistrar.resultMatchers) {
|
||||
if (state.indexVariable != null && !matcher.indexVariableUsePossible) continue
|
||||
if (restContainsEmbeddedBreakOrContinue && !matcher.embeddedBreakOrContinuePossible) continue
|
||||
MatchersLoop@
|
||||
for (matcher in MatcherRegistrar.matchers) {
|
||||
if (state.indexVariable != null && !matcher.indexVariableAllowed) continue
|
||||
|
||||
val match = matcher.match(state)
|
||||
if (match != null) {
|
||||
if (!inputVariableUsed
|
||||
&& (match.sequenceTransformations.any { it.shouldUseInputVariable }
|
||||
|| match.resultTransformation.shouldUseInputVariable)) {
|
||||
return null
|
||||
}
|
||||
if (!inputVariableUsed && match.allTransformations.any { it.shouldUseInputVariable }) return null
|
||||
|
||||
sequenceTransformations.addAll(match.sequenceTransformations)
|
||||
return ResultTransformationMatch(match.resultTransformation, sequenceTransformations)
|
||||
.let { mergeTransformations(it) }
|
||||
.let { MatchResult(sequenceExpression, it, state.initializationStatementsToDelete) }
|
||||
.check { checkSmartCastsPreserved(loop, it) }
|
||||
}
|
||||
}
|
||||
|
||||
for (matcher in MatcherRegistrar.sequenceMatchers) {
|
||||
val match = matcher.match(state)
|
||||
if (match != null) {
|
||||
if (!inputVariableUsed && match.transformations.any { it.shouldUseInputVariable }) {
|
||||
return null
|
||||
when (match) {
|
||||
is TransformationMatch.Sequence -> {
|
||||
// check that old input variable is not needed anymore
|
||||
var newState = match.newState
|
||||
if (state.inputVariable != newState.inputVariable && state.inputVariable.hasUsages(newState.statements)) return null
|
||||
|
||||
if (state.indexVariable != null && match.sequenceTransformations.any { it.affectsIndex }) {
|
||||
// index variable is still needed but index in the new sequence is different
|
||||
if (state.indexVariable!!.hasUsages(newState.statements)) return null
|
||||
newState = newState.copy(indexVariable = null)
|
||||
}
|
||||
|
||||
if (restContainsEmbeddedBreakOrContinue && !matcher.embeddedBreakOrContinuePossible) {
|
||||
val countBefore = state.statements.sumBy { it.countEmbeddedBreaksAndContinues() }
|
||||
val countAfter = newState.statements.sumBy { it.countEmbeddedBreaksAndContinues() }
|
||||
if (countAfter != countBefore) continue@MatchersLoop // some embedded break or continue in the matched part
|
||||
}
|
||||
|
||||
state = newState
|
||||
continue@MatchLoop
|
||||
}
|
||||
|
||||
is TransformationMatch.Result -> {
|
||||
if (restContainsEmbeddedBreakOrContinue && !matcher.embeddedBreakOrContinuePossible) continue@MatchersLoop
|
||||
|
||||
return TransformationMatch.Result(match.resultTransformation, sequenceTransformations)
|
||||
.let { mergeTransformations(it) }
|
||||
.let { MatchResult(sequenceExpression, it, state.initializationStatementsToDelete) }
|
||||
.check { checkSmartCastsPreserved(loop, it) }
|
||||
}
|
||||
}
|
||||
|
||||
// check that old input variable is not needed anymore
|
||||
var newState = match.newState
|
||||
if (state.inputVariable != newState.inputVariable && state.inputVariable.hasUsages(newState.statements)) return null
|
||||
|
||||
if (state.indexVariable != null && match.transformations.any { it.affectsIndex }) {
|
||||
// index variable is still needed but index in the new sequence is different
|
||||
if (state.indexVariable!!.hasUsages(newState.statements)) return null
|
||||
newState = newState.copy(indexVariable = null)
|
||||
}
|
||||
|
||||
if (restContainsEmbeddedBreakOrContinue && !matcher.embeddedBreakOrContinuePossible) {
|
||||
val countBefore = state.statements.sumBy { it.countEmbeddedBreaksAndContinues() }
|
||||
val countAfter = newState.statements.sumBy { it.countEmbeddedBreaksAndContinues() }
|
||||
if (countAfter != countBefore) continue // some embedded break or continue in the matched part
|
||||
}
|
||||
|
||||
sequenceTransformations.addAll(match.transformations)
|
||||
state = newState
|
||||
continue@MatchLoop
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,7 +269,7 @@ private fun MatchResult.generateCallChain(loop: KtForExpression): KtExpression {
|
||||
return callChain
|
||||
}
|
||||
|
||||
private fun mergeTransformations(match: ResultTransformationMatch): ResultTransformationMatch {
|
||||
private fun mergeTransformations(match: TransformationMatch.Result): TransformationMatch.Result {
|
||||
val transformations = ArrayList<Transformation>().apply { addAll(match.sequenceTransformations); add(match.resultTransformation) }
|
||||
|
||||
var anyChange: Boolean
|
||||
@@ -302,6 +292,6 @@ private fun mergeTransformations(match: ResultTransformationMatch): ResultTransf
|
||||
} while(anyChange)
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return ResultTransformationMatch(transformations.last() as ResultTransformation, transformations.dropLast(1) as List<SequenceTransformation>)
|
||||
return TransformationMatch.Result(transformations.last() as ResultTransformation, transformations.dropLast(1) as List<SequenceTransformation>)
|
||||
}
|
||||
|
||||
|
||||
+10
-10
@@ -84,11 +84,11 @@ class AddToCollectionTransformation(
|
||||
* collection.add(...)
|
||||
* }
|
||||
*/
|
||||
object Matcher : ResultTransformationMatcher {
|
||||
override val indexVariableUsePossible: Boolean
|
||||
object Matcher : TransformationMatcher {
|
||||
override val indexVariableAllowed: Boolean
|
||||
get() = true
|
||||
|
||||
override fun match(state: MatchingState): ResultTransformationMatch? {
|
||||
override fun match(state: MatchingState): TransformationMatch.Result? {
|
||||
val statement = state.statements.singleOrNull() ?: return null
|
||||
//TODO: it can be implicit 'this' too
|
||||
val qualifiedExpression = statement as? KtDotQualifiedExpression ?: return null
|
||||
@@ -108,11 +108,11 @@ class AddToCollectionTransformation(
|
||||
}
|
||||
|
||||
if (state.indexVariable == null && argumentValue.isVariableReference(state.inputVariable)) {
|
||||
return ResultTransformationMatch(AddToCollectionTransformation(state.outerLoop, targetCollection))
|
||||
return TransformationMatch.Result(AddToCollectionTransformation(state.outerLoop, targetCollection))
|
||||
}
|
||||
else {
|
||||
//TODO: recognize "?: continue" in the argument
|
||||
return ResultTransformationMatch(MapToTransformation.create(
|
||||
return TransformationMatch.Result(MapToTransformation.create(
|
||||
state.outerLoop, state.inputVariable, state.indexVariable, targetCollection, argumentValue, mapNotNull = false))
|
||||
}
|
||||
}
|
||||
@@ -121,7 +121,7 @@ class AddToCollectionTransformation(
|
||||
state: MatchingState,
|
||||
targetCollection: KtExpression,
|
||||
addOperationArgument: KtExpression
|
||||
): ResultTransformationMatch? {
|
||||
): TransformationMatch.Result? {
|
||||
val collectionInitialization = targetCollection.detectInitializationBeforeLoop(state.outerLoop, checkNoOtherUsagesInLoop = true) ?: return null
|
||||
val collectionKind = collectionInitialization.initializer.isSimpleCollectionInstantiation() ?: return null
|
||||
val argumentIsInputVariable = addOperationArgument.isVariableReference(state.inputVariable)
|
||||
@@ -136,13 +136,13 @@ class AddToCollectionTransformation(
|
||||
val mapTransformation = MapTransformation(state.outerLoop, state.inputVariable, null, addOperationArgument, mapNotNull = false)
|
||||
AssignSequenceTransformationResultTransformation(mapTransformation, collectionInitialization)
|
||||
}
|
||||
return ResultTransformationMatch(transformation)
|
||||
return TransformationMatch.Result(transformation)
|
||||
}
|
||||
|
||||
canChangeInitializerType(collectionInitialization, KotlinBuiltIns.FQ_NAMES.mutableList, state.outerLoop) -> {
|
||||
if (argumentIsInputVariable) {
|
||||
val transformation = AssignToMutableListTransformation(state.outerLoop, collectionInitialization)
|
||||
return ResultTransformationMatch(transformation)
|
||||
return TransformationMatch.Result(transformation)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,11 +162,11 @@ class AddToCollectionTransformation(
|
||||
}
|
||||
|
||||
if (argumentIsInputVariable) {
|
||||
return ResultTransformationMatch(assignToSetTransformation)
|
||||
return TransformationMatch.Result(assignToSetTransformation)
|
||||
}
|
||||
else {
|
||||
val mapTransformation = MapTransformation(state.outerLoop, state.inputVariable, null, addOperationArgument, mapNotNull = false)
|
||||
return ResultTransformationMatch(assignToSetTransformation, mapTransformation)
|
||||
return TransformationMatch.Result(assignToSetTransformation, mapTransformation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -68,11 +68,11 @@ class CountTransformation(
|
||||
* variable++ (or ++variable)
|
||||
* }
|
||||
*/
|
||||
object Matcher : ResultTransformationMatcher {
|
||||
override val indexVariableUsePossible: Boolean
|
||||
object Matcher : TransformationMatcher {
|
||||
override val indexVariableAllowed: Boolean
|
||||
get() = false
|
||||
|
||||
override fun match(state: MatchingState): ResultTransformationMatch? {
|
||||
override fun match(state: MatchingState): TransformationMatch.Result? {
|
||||
val operand = state.statements.singleOrNull()?.isPlusPlusOf() ?: return null
|
||||
val initialization = operand.detectInitializationBeforeLoop(state.outerLoop, checkNoOtherUsagesInLoop = true) ?: return null
|
||||
|
||||
@@ -82,7 +82,7 @@ class CountTransformation(
|
||||
if (!KotlinBuiltIns.isInt(variableType)) return null
|
||||
|
||||
val transformation = CountTransformation(state.outerLoop, state.inputVariable, initialization, null)
|
||||
return ResultTransformationMatch(transformation)
|
||||
return TransformationMatch.Result(transformation)
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
-67
@@ -16,30 +16,21 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.intentions.loopToCallChain.result
|
||||
|
||||
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.KtBinaryExpression
|
||||
import org.jetbrains.kotlin.psi.KtBreakExpression
|
||||
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.AssignToVariableResultTransformation
|
||||
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.ChainedCallGenerator
|
||||
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.FindOperatorGenerator
|
||||
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.VariableInitialization
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.psi.KtForExpression
|
||||
|
||||
class FindAndAssignTransformation(
|
||||
loop: KtForExpression,
|
||||
private val generator: FindOperatorGenerator,
|
||||
initialization: VariableInitialization,
|
||||
private val filter: KtExpression? = null
|
||||
initialization: VariableInitialization
|
||||
) : AssignToVariableResultTransformation(loop, initialization) {
|
||||
|
||||
override fun mergeWithPrevious(previousTransformation: SequenceTransformation): ResultTransformation? {
|
||||
if (previousTransformation !is FilterTransformation) return null
|
||||
if (previousTransformation.indexVariable != null) return null
|
||||
assert(filter == null) { "Should not happen because no 2 consecutive FilterTransformation's possible"}
|
||||
return FindAndAssignTransformation(loop, generator, initialization, previousTransformation.effectiveCondition())
|
||||
}
|
||||
|
||||
override val presentation: String
|
||||
get() = generator.functionName + (if (filter != null) "{}" else "()")
|
||||
get() = generator.presentation
|
||||
|
||||
override val chainCallCount: Int
|
||||
get() = generator.chainCallCount
|
||||
@@ -48,57 +39,6 @@ class FindAndAssignTransformation(
|
||||
get() = generator.shouldUseInputVariable
|
||||
|
||||
override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression {
|
||||
return generator.generate(chainedCallGenerator, filter)
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches:
|
||||
* val variable = ...
|
||||
* for (...) {
|
||||
* ...
|
||||
* variable = ...
|
||||
* break
|
||||
* }
|
||||
* or
|
||||
* val variable = ...
|
||||
* for (...) {
|
||||
* ...
|
||||
* variable = ...
|
||||
* }
|
||||
*/
|
||||
object Matcher : ResultTransformationMatcher {
|
||||
override val indexVariableUsePossible: Boolean
|
||||
get() = false
|
||||
|
||||
override fun match(state: MatchingState): ResultTransformationMatch? {
|
||||
when (state.statements.size) {
|
||||
1 -> {}
|
||||
|
||||
2 -> {
|
||||
val breakExpression = state.statements.last() as? KtBreakExpression ?: return null
|
||||
if (breakExpression.targetLoop() != state.outerLoop) return null
|
||||
}
|
||||
|
||||
else -> return null
|
||||
}
|
||||
val findFirst = state.statements.size == 2
|
||||
|
||||
val binaryExpression = state.statements.first() as? KtBinaryExpression ?: return null
|
||||
if (binaryExpression.operationToken != KtTokens.EQ) return null
|
||||
val left = binaryExpression.left ?: return null
|
||||
val right = binaryExpression.right ?: return null
|
||||
|
||||
val initialization = left.detectInitializationBeforeLoop(state.outerLoop, checkNoOtherUsagesInLoop = true) ?: return null
|
||||
|
||||
if (initialization.variable.countUsages(state.outerLoop) != 1) return null // this should be the only usage of this variable inside the loop
|
||||
|
||||
// we do not try to convert anything if the initializer is not compile-time constant because of possible side-effects
|
||||
if (!initialization.initializer.isConstant()) return null
|
||||
|
||||
val generator = buildFindOperationGenerator(right, initialization.initializer, state.inputVariable, findFirst) ?: return null
|
||||
|
||||
val transformation = FindAndAssignTransformation(state.outerLoop, generator, initialization)
|
||||
return ResultTransformationMatch(transformation)
|
||||
}
|
||||
return generator.generate(chainedCallGenerator)
|
||||
}
|
||||
}
|
||||
+3
-39
@@ -17,7 +17,6 @@
|
||||
package org.jetbrains.kotlin.idea.intentions.loopToCallChain.result
|
||||
|
||||
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.*
|
||||
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.FilterTransformation
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.psi.KtForExpression
|
||||
import org.jetbrains.kotlin.psi.KtReturnExpression
|
||||
@@ -26,21 +25,13 @@ import org.jetbrains.kotlin.psi.psiUtil.PsiChildRange
|
||||
class FindAndReturnTransformation(
|
||||
override val loop: KtForExpression,
|
||||
private val generator: FindOperatorGenerator,
|
||||
private val endReturn: KtReturnExpression,
|
||||
private val filter: KtExpression? = null
|
||||
private val endReturn: KtReturnExpression
|
||||
) : ResultTransformation {
|
||||
|
||||
override fun mergeWithPrevious(previousTransformation: SequenceTransformation): ResultTransformation? {
|
||||
if (previousTransformation !is FilterTransformation) return null
|
||||
if (previousTransformation.indexVariable != null) return null
|
||||
assert(filter == null) { "Should not happen because no 2 consecutive FilterTransformation's possible"}
|
||||
return FindAndReturnTransformation(loop, generator, endReturn, previousTransformation.effectiveCondition())
|
||||
}
|
||||
|
||||
override val commentSavingRange = PsiChildRange(loop.unwrapIfLabeled(), endReturn)
|
||||
|
||||
override val presentation: String
|
||||
get() = generator.functionName + (if (filter != null) "{}" else "()")
|
||||
get() = generator.presentation
|
||||
|
||||
override val chainCallCount: Int
|
||||
get() = generator.chainCallCount
|
||||
@@ -49,7 +40,7 @@ class FindAndReturnTransformation(
|
||||
get() = generator.shouldUseInputVariable
|
||||
|
||||
override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression {
|
||||
return generator.generate(chainedCallGenerator, filter)
|
||||
return generator.generate(chainedCallGenerator)
|
||||
}
|
||||
|
||||
override fun convertLoop(resultCallChain: KtExpression, commentSavingRangeHolder: CommentSavingRangeHolder): KtExpression {
|
||||
@@ -57,31 +48,4 @@ class FindAndReturnTransformation(
|
||||
loop.deleteWithLabels()
|
||||
return endReturn
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches:
|
||||
* for (...) {
|
||||
* ...
|
||||
* return ...
|
||||
* }
|
||||
* return ...
|
||||
*/
|
||||
object Matcher : ResultTransformationMatcher {
|
||||
override val indexVariableUsePossible: Boolean
|
||||
get() = false
|
||||
|
||||
override fun match(state: MatchingState): ResultTransformationMatch? {
|
||||
val returnInLoop = state.statements.singleOrNull() as? KtReturnExpression ?: return null
|
||||
val returnAfterLoop = state.outerLoop.nextStatement() as? KtReturnExpression ?: return null
|
||||
if (returnInLoop.getLabelName() != returnAfterLoop.getLabelName()) return null
|
||||
|
||||
val returnValueInLoop = returnInLoop.returnedExpression ?: return null
|
||||
val returnValueAfterLoop = returnAfterLoop.returnedExpression ?: return null
|
||||
|
||||
val generator = buildFindOperationGenerator(returnValueInLoop, returnValueAfterLoop, state.inputVariable, findFirst = true) ?: return null
|
||||
|
||||
val transformation = FindAndReturnTransformation(state.outerLoop, generator, returnAfterLoop)
|
||||
return ResultTransformationMatch(transformation)
|
||||
}
|
||||
}
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* 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.idea.intentions.loopToCallChain.sequence.FilterTransformation
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.psi.KtBinaryExpression
|
||||
import org.jetbrains.kotlin.psi.KtBreakExpression
|
||||
import org.jetbrains.kotlin.psi.KtReturnExpression
|
||||
|
||||
/**
|
||||
* Matches:
|
||||
* val variable = ...
|
||||
* for (...) {
|
||||
* ...
|
||||
* variable = ...
|
||||
* break
|
||||
* }
|
||||
* or
|
||||
* val variable = ...
|
||||
* for (...) {
|
||||
* ...
|
||||
* variable = ...
|
||||
* }
|
||||
* or
|
||||
* for (...) {
|
||||
* ...
|
||||
* return ...
|
||||
* }
|
||||
* return ...
|
||||
*/
|
||||
object FindTransformationMatcher : TransformationMatcher {
|
||||
override val indexVariableAllowed: Boolean
|
||||
get() = false
|
||||
|
||||
override fun match(state: MatchingState): TransformationMatch.Result? {
|
||||
return matchWithFilterBefore(state, null)
|
||||
}
|
||||
|
||||
fun matchWithFilterBefore(state: MatchingState, filterTransformation: FilterTransformation?): TransformationMatch.Result? {
|
||||
matchReturn(state, filterTransformation)?.let { return it }
|
||||
|
||||
when (state.statements.size) {
|
||||
1 -> { }
|
||||
|
||||
2 -> {
|
||||
val breakExpression = state.statements.last() as? KtBreakExpression ?: return null
|
||||
if (breakExpression.targetLoop() != state.outerLoop) return null
|
||||
}
|
||||
|
||||
else -> return null
|
||||
}
|
||||
val findFirst = state.statements.size == 2
|
||||
|
||||
val binaryExpression = state.statements.first() as? KtBinaryExpression ?: return null
|
||||
if (binaryExpression.operationToken != KtTokens.EQ) return null
|
||||
val left = binaryExpression.left ?: return null
|
||||
val right = binaryExpression.right ?: return null
|
||||
|
||||
val initialization = left.detectInitializationBeforeLoop(state.outerLoop, checkNoOtherUsagesInLoop = true) ?: return null
|
||||
|
||||
if (initialization.variable.countUsages(state.outerLoop) != 1) return null // this should be the only usage of this variable inside the loop
|
||||
|
||||
// we do not try to convert anything if the initializer is not compile-time constant because of possible side-effects
|
||||
if (!initialization.initializer.isConstant()) return null
|
||||
|
||||
val generator = buildFindOperationGenerator(right, initialization.initializer, state.inputVariable, state.indexVariable, filterTransformation, findFirst)
|
||||
?: return null
|
||||
|
||||
val transformation = FindAndAssignTransformation(state.outerLoop, generator, initialization)
|
||||
return TransformationMatch.Result(transformation)
|
||||
}
|
||||
|
||||
private fun matchReturn(state: MatchingState, filterTransformation: FilterTransformation?): TransformationMatch.Result? {
|
||||
val returnInLoop = state.statements.singleOrNull() as? KtReturnExpression ?: return null
|
||||
val returnAfterLoop = state.outerLoop.nextStatement() as? KtReturnExpression ?: return null
|
||||
if (returnInLoop.getLabelName() != returnAfterLoop.getLabelName()) return null
|
||||
|
||||
val returnValueInLoop = returnInLoop.returnedExpression ?: return null
|
||||
val returnValueAfterLoop = returnAfterLoop.returnedExpression ?: return null
|
||||
|
||||
val generator = buildFindOperationGenerator(returnValueInLoop, returnValueAfterLoop,
|
||||
state.inputVariable, state.indexVariable, filterTransformation, findFirst = true)
|
||||
?: return null
|
||||
|
||||
val transformation = FindAndReturnTransformation(state.outerLoop, generator, returnAfterLoop)
|
||||
return TransformationMatch.Result(transformation)
|
||||
}
|
||||
}
|
||||
+12
-3
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence
|
||||
|
||||
import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isNullExpression
|
||||
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.*
|
||||
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.result.FindTransformationMatcher
|
||||
import org.jetbrains.kotlin.idea.intentions.negate
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
@@ -73,9 +74,14 @@ class FilterTransformation(
|
||||
* if (<condition>) break
|
||||
* ...
|
||||
* }
|
||||
*
|
||||
* plus optionally consequent find operation (assignment or return)
|
||||
*/
|
||||
object Matcher : SequenceTransformationMatcher {
|
||||
override fun match(state: MatchingState): SequenceTransformationMatch? {
|
||||
object Matcher : TransformationMatcher {
|
||||
override val indexVariableAllowed: Boolean
|
||||
get() = true
|
||||
|
||||
override fun match(state: MatchingState): TransformationMatch? {
|
||||
// we merge filter transformations here instead of FilterTransformation.mergeWithPrevious() because of filterIndexed that won't merge otherwise
|
||||
|
||||
var (transformation, currentState) = matchOneTransformation(state) ?: return null
|
||||
@@ -93,9 +99,12 @@ class FilterTransformation(
|
||||
transformation = FilterTransformation(state.outerLoop, transformation.inputVariable, indexVariable, mergedCondition, isInverse = false) //TODO: build filterNot in some cases?
|
||||
currentState = nextState
|
||||
}
|
||||
|
||||
FindTransformationMatcher.matchWithFilterBefore(currentState, transformation)
|
||||
?.let { return it }
|
||||
}
|
||||
|
||||
return SequenceTransformationMatch(transformation, currentState)
|
||||
return TransformationMatch.Sequence(transformation, currentState)
|
||||
}
|
||||
|
||||
private fun matchOneTransformation(state: MatchingState): Pair<SequenceTransformation, MatchingState>? {
|
||||
|
||||
+7
-4
@@ -49,8 +49,11 @@ class FlatMapTransformation(
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
object Matcher : SequenceTransformationMatcher {
|
||||
override fun match(state: MatchingState): SequenceTransformationMatch? {
|
||||
object Matcher : TransformationMatcher {
|
||||
override val indexVariableAllowed: Boolean
|
||||
get() = true
|
||||
|
||||
override fun match(state: MatchingState): TransformationMatch.Sequence? {
|
||||
val nestedLoop = state.statements.singleOrNull() as? KtForExpression ?: return null
|
||||
|
||||
val transform = nestedLoop.loopRange ?: return null
|
||||
@@ -73,7 +76,7 @@ class FlatMapTransformation(
|
||||
statements = listOf(nestedLoopBody),
|
||||
inputVariable = newInputVariable
|
||||
)
|
||||
return SequenceTransformationMatch(listOf(mapIndexedTransformation, flatMapTransformation), newState)
|
||||
return TransformationMatch.Sequence(listOf(mapIndexedTransformation, flatMapTransformation), newState)
|
||||
}
|
||||
|
||||
val transformation = FlatMapTransformation(state.outerLoop, state.inputVariable, transform)
|
||||
@@ -82,7 +85,7 @@ class FlatMapTransformation(
|
||||
statements = listOf(nestedLoopBody),
|
||||
inputVariable = newInputVariable
|
||||
)
|
||||
return SequenceTransformationMatch(transformation, newState)
|
||||
return TransformationMatch.Sequence(transformation, newState)
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-4
@@ -22,9 +22,11 @@ import org.jetbrains.kotlin.psi.KtContinueExpression
|
||||
import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
|
||||
|
||||
object IntroduceIndexMatcher : SequenceTransformationMatcher {
|
||||
override fun match(state: MatchingState): SequenceTransformationMatch? {
|
||||
if (state.indexVariable != null) return null // old index variable is still needed - cannot introduce another one
|
||||
object IntroduceIndexMatcher : TransformationMatcher {
|
||||
override val indexVariableAllowed: Boolean
|
||||
get() = false // old index variable is still needed - cannot introduce another one
|
||||
|
||||
override fun match(state: MatchingState): TransformationMatch.Sequence? {
|
||||
if (state.statements.size < 2) return null
|
||||
val operand = state.statements.last().isPlusPlusOf() ?: return null
|
||||
val restStatements = state.statements.dropLast(1)
|
||||
@@ -52,6 +54,6 @@ object IntroduceIndexMatcher : SequenceTransformationMatcher {
|
||||
val newState = state.copy(statements = restStatements,
|
||||
indexVariable = variable,
|
||||
initializationStatementsToDelete = state.initializationStatementsToDelete + variableInitialization.initializationStatement)
|
||||
return SequenceTransformationMatch(emptyList(), newState)
|
||||
return TransformationMatch.Sequence(emptyList(), newState)
|
||||
}
|
||||
}
|
||||
+7
-4
@@ -54,11 +54,14 @@ class MapTransformation(
|
||||
* ...
|
||||
* }
|
||||
*/
|
||||
object Matcher : SequenceTransformationMatcher {
|
||||
object Matcher : TransformationMatcher {
|
||||
override val indexVariableAllowed: Boolean
|
||||
get() = true
|
||||
|
||||
override val embeddedBreakOrContinuePossible: Boolean
|
||||
get() = true
|
||||
|
||||
override fun match(state: MatchingState): SequenceTransformationMatch? {
|
||||
override fun match(state: MatchingState): TransformationMatch.Sequence? {
|
||||
val declaration = state.statements.firstOrNull() as? KtProperty ?: return null //TODO: support multi-variables
|
||||
val initializer = declaration.initializer ?: return null
|
||||
if (declaration.hasWriteUsages()) return null
|
||||
@@ -76,7 +79,7 @@ class MapTransformation(
|
||||
else
|
||||
MapTransformation(state.outerLoop, state.inputVariable, null, mapping, mapNotNull = true)
|
||||
val newState = state.copy(statements = restStatements, inputVariable = declaration)
|
||||
return SequenceTransformationMatch(transformation, newState)
|
||||
return TransformationMatch.Sequence(transformation, newState)
|
||||
}
|
||||
|
||||
if (initializer.containsEmbeddedBreakOrContinue()) return null
|
||||
@@ -86,7 +89,7 @@ class MapTransformation(
|
||||
else
|
||||
MapTransformation(state.outerLoop, state.inputVariable, null, initializer, mapNotNull = false)
|
||||
val newState = state.copy(statements = restStatements, inputVariable = declaration)
|
||||
return SequenceTransformationMatch(transformation, newState)
|
||||
return TransformationMatch.Sequence(transformation, newState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// WITH_RUNTIME
|
||||
// INTENTION_TEXT: "Replace with 'indexOfFirst{}'"
|
||||
fun foo(list: List<String>) {
|
||||
var result = -1
|
||||
<caret>for ((index, s) in list.withIndex()) {
|
||||
if (s.length > 0) {
|
||||
result = index
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// WITH_RUNTIME
|
||||
// INTENTION_TEXT: "Replace with 'indexOfFirst{}'"
|
||||
fun foo(list: List<String>) {
|
||||
val <caret>result = list.indexOfFirst { it.length > 0 }
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// WITH_RUNTIME
|
||||
// INTENTION_TEXT: "Replace with 'indexOfFirst{}'"
|
||||
fun foo(list: List<String>): Int {
|
||||
<caret>for ((index, s) in list.withIndex()) {
|
||||
if (s.length > 0) {
|
||||
return index
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// WITH_RUNTIME
|
||||
// INTENTION_TEXT: "Replace with 'indexOfFirst{}'"
|
||||
fun foo(list: List<String>): Int {
|
||||
<caret>return list.indexOfFirst { it.length > 0 }
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// WITH_RUNTIME
|
||||
// IS_APPLICABLE: false
|
||||
fun foo(list: List<String>) {
|
||||
var result = -1
|
||||
<caret>for ((index, s) in list.withIndex()) {
|
||||
if (s.length > index) {
|
||||
result = index
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// WITH_RUNTIME
|
||||
// INTENTION_TEXT: "Replace with 'indexOfLast{}'"
|
||||
fun foo(list: List<String>) {
|
||||
var result = -1
|
||||
<caret>for ((index, s) in list.withIndex()) {
|
||||
if (s.length > 0) {
|
||||
result = index
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// WITH_RUNTIME
|
||||
// INTENTION_TEXT: "Replace with 'indexOfLast{}'"
|
||||
fun foo(list: List<String>) {
|
||||
val <caret>result = list.indexOfLast { it.length > 0 }
|
||||
}
|
||||
Reference in New Issue
Block a user