Initial support for indexed transformations
This commit is contained in:
+1
-1
@@ -35,7 +35,7 @@ class LoopToCallChainIntention : SelfTargetingRangeIntention<KtForExpression>(
|
||||
) {
|
||||
override fun applicabilityRange(element: KtForExpression): TextRange? {
|
||||
val match = match(element)
|
||||
text = if (match != null) "Replace with '${match.buildPresentation()}'" else defaultText
|
||||
text = if (match != null) "Replace with '${match.transformationMatch.buildPresentation()}'" else defaultText
|
||||
return if (match != null) element.forKeyword.textRange else null
|
||||
}
|
||||
|
||||
|
||||
@@ -86,6 +86,9 @@ data class MatchingState(
|
||||
val innerLoop: KtForExpression,
|
||||
val statements: Collection<KtExpression>,
|
||||
val inputVariable: KtCallableDeclaration,
|
||||
/**
|
||||
* Matchers can assume that indexVariable is null if it's not used in the rest of the loop
|
||||
*/
|
||||
val indexVariable: KtCallableDeclaration?
|
||||
)
|
||||
|
||||
@@ -113,6 +116,8 @@ class SequenceTransformationMatch(
|
||||
*/
|
||||
interface ResultTransformationMatcher {
|
||||
fun match(state: MatchingState): ResultTransformationMatch?
|
||||
|
||||
val indexVariableUsePossible: Boolean
|
||||
}
|
||||
|
||||
class ResultTransformationMatch(
|
||||
|
||||
@@ -29,6 +29,7 @@ import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.FlatMapTran
|
||||
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.MapTransformation
|
||||
import org.jetbrains.kotlin.idea.util.CommentSaver
|
||||
import org.jetbrains.kotlin.idea.util.getResolutionScope
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
|
||||
@@ -54,14 +55,21 @@ object MatcherRegistrar {
|
||||
)
|
||||
}
|
||||
|
||||
fun match(loop: KtForExpression): ResultTransformationMatch? {
|
||||
data class MatchResult(
|
||||
val sequenceExpression: KtExpression,
|
||||
val transformationMatch: ResultTransformationMatch
|
||||
)
|
||||
|
||||
fun match(loop: KtForExpression): MatchResult? {
|
||||
val (inputVariable, indexVariable, sequenceExpression) = extractLoopData(loop) ?: return null
|
||||
|
||||
val sequenceTransformations = ArrayList<SequenceTransformation>()
|
||||
var state = MatchingState(
|
||||
outerLoop = loop,
|
||||
innerLoop = loop,
|
||||
statements = listOf(loop.body ?: return null),
|
||||
inputVariable = loop.loopParameter ?: return null,
|
||||
indexVariable = null
|
||||
inputVariable = inputVariable,
|
||||
indexVariable = indexVariable
|
||||
)
|
||||
|
||||
MatchLoop@
|
||||
@@ -73,7 +81,14 @@ fun match(loop: KtForExpression): ResultTransformationMatch? {
|
||||
|
||||
val inputVariableUsed = state.inputVariable.hasUsages(state.statements)
|
||||
|
||||
// drop index variable if it's not used anymore
|
||||
if (state.indexVariable != null && !state.indexVariable!!.hasUsages(state.statements)) {
|
||||
state = state.copy(indexVariable = null)
|
||||
}
|
||||
|
||||
for (matcher in MatcherRegistrar.resultMatchers) {
|
||||
if (state.indexVariable != null && !matcher.indexVariableUsePossible) continue
|
||||
|
||||
val match = matcher.match(state)
|
||||
if (match != null) {
|
||||
if (!inputVariableUsed
|
||||
@@ -85,6 +100,7 @@ fun match(loop: KtForExpression): ResultTransformationMatch? {
|
||||
sequenceTransformations.addAll(match.sequenceTransformations)
|
||||
return ResultTransformationMatch(match.resultTransformation, sequenceTransformations)
|
||||
.let { mergeTransformations(it) }
|
||||
.let { MatchResult(sequenceExpression, it) }
|
||||
.check { checkSmartCastsPreserved(loop, it) }
|
||||
}
|
||||
}
|
||||
@@ -96,10 +112,16 @@ fun match(loop: KtForExpression): ResultTransformationMatch? {
|
||||
return null
|
||||
}
|
||||
|
||||
val newState = match.newState
|
||||
var newState = match.newState
|
||||
// check that old input variable is not needed anymore
|
||||
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)
|
||||
}
|
||||
|
||||
sequenceTransformations.addAll(match.transformations)
|
||||
state = newState
|
||||
continue@MatchLoop
|
||||
@@ -111,19 +133,44 @@ fun match(loop: KtForExpression): ResultTransformationMatch? {
|
||||
}
|
||||
|
||||
//TODO: offer to use of .asSequence() as an option
|
||||
fun convertLoop(loop: KtForExpression, matchResult: ResultTransformationMatch): KtExpression {
|
||||
val commentSaver = CommentSaver(matchResult.resultTransformation.commentSavingRange)
|
||||
fun convertLoop(loop: KtForExpression, matchResult: MatchResult): KtExpression {
|
||||
val resultTransformation = matchResult.transformationMatch.resultTransformation
|
||||
val commentSaver = CommentSaver(resultTransformation.commentSavingRange)
|
||||
|
||||
val callChain = matchResult.generateCallChain(loop)
|
||||
|
||||
val result = matchResult.resultTransformation.convertLoop(callChain)
|
||||
val result = resultTransformation.convertLoop(callChain)
|
||||
|
||||
commentSaver.restore(matchResult.resultTransformation.commentRestoringRange(result))
|
||||
commentSaver.restore(resultTransformation.commentRestoringRange(result))
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun checkSmartCastsPreserved(loop: KtForExpression, matchResult: ResultTransformationMatch): Boolean {
|
||||
data class LoopData(
|
||||
val inputVariable: KtCallableDeclaration,
|
||||
val indexVariable: KtCallableDeclaration?,
|
||||
val sequenceExpression: KtExpression)
|
||||
|
||||
private fun extractLoopData(loop: KtForExpression): LoopData? {
|
||||
val loopRange = loop.loopRange ?: return null
|
||||
|
||||
//TODO: recognize other patterns for loop with index
|
||||
val destructuringParameter = loop.destructuringParameter
|
||||
if (destructuringParameter != null && destructuringParameter.entries.size == 2) {
|
||||
val qualifiedExpression = loopRange as? KtDotQualifiedExpression
|
||||
if (qualifiedExpression != null) {
|
||||
val call = qualifiedExpression.selectorExpression as? KtCallExpression
|
||||
//TODO: check that it's the correct "withIndex"
|
||||
if (call != null && call.calleeExpression.isSimpleName(Name.identifier("withIndex")) && call.valueArguments.isEmpty()) {
|
||||
return LoopData(destructuringParameter.entries[1], destructuringParameter.entries[0], qualifiedExpression.receiverExpression)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return LoopData(loop.loopParameter ?: return null, null, loopRange)
|
||||
}
|
||||
|
||||
private fun checkSmartCastsPreserved(loop: KtForExpression, matchResult: MatchResult): Boolean {
|
||||
val bindingContext = loop.analyze(BodyResolveMode.FULL)
|
||||
|
||||
val SMARTCAST_KEY = Key<KotlinType>("SMARTCAST_KEY")
|
||||
@@ -174,9 +221,9 @@ private fun checkSmartCastsPreserved(loop: KtForExpression, matchResult: ResultT
|
||||
}
|
||||
}
|
||||
|
||||
private fun ResultTransformationMatch.generateCallChain(loop: KtForExpression): KtExpression {
|
||||
var sequenceTransformations = sequenceTransformations
|
||||
var resultTransformation = resultTransformation
|
||||
private fun MatchResult.generateCallChain(loop: KtForExpression): KtExpression {
|
||||
var sequenceTransformations = transformationMatch.sequenceTransformations
|
||||
var resultTransformation = transformationMatch.resultTransformation
|
||||
while(true) {
|
||||
val last = sequenceTransformations.lastOrNull() ?: break
|
||||
resultTransformation = resultTransformation.mergeWithPrevious(last) ?: break
|
||||
@@ -186,7 +233,7 @@ private fun ResultTransformationMatch.generateCallChain(loop: KtForExpression):
|
||||
val chainCallCount = sequenceTransformations.sumBy { it.chainCallCount } + resultTransformation.chainCallCount
|
||||
val lineBreak = if (chainCallCount > 1) "\n" else ""
|
||||
|
||||
var callChain = loop.loopRange!!
|
||||
var callChain = sequenceExpression
|
||||
|
||||
val psiFactory = KtPsiFactory(loop)
|
||||
val chainedCallGenerator = object : ChainedCallGenerator {
|
||||
|
||||
+17
-11
@@ -24,6 +24,7 @@ import org.jetbrains.kotlin.idea.imports.importableFqName
|
||||
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.*
|
||||
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.MapIndexedTransformation
|
||||
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.MapTransformation
|
||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
@@ -33,14 +34,13 @@ import org.jetbrains.kotlin.renderer.render
|
||||
|
||||
class AddToCollectionTransformation(
|
||||
loop: KtForExpression,
|
||||
private val inputVariable: KtCallableDeclaration,
|
||||
private val targetCollection: KtExpression
|
||||
) : ReplaceLoopResultTransformation(loop) {
|
||||
|
||||
override fun mergeWithPrevious(previousTransformation: SequenceTransformation): ResultTransformation? {
|
||||
return when (previousTransformation) {
|
||||
is FilterTransformation -> {
|
||||
FilterToTransformation.create(loop, inputVariable, targetCollection, previousTransformation.effectiveCondition()) //TODO: use filterNotTo?
|
||||
FilterToTransformation.create(loop, previousTransformation.inputVariable, targetCollection, previousTransformation.effectiveCondition()) //TODO: use filterNotTo?
|
||||
}
|
||||
|
||||
is MapTransformation -> {
|
||||
@@ -80,10 +80,10 @@ class AddToCollectionTransformation(
|
||||
* }
|
||||
*/
|
||||
object Matcher : ResultTransformationMatcher {
|
||||
override fun match(state: MatchingState): ResultTransformationMatch? {
|
||||
//TODO: pass indexVariable as null if not used
|
||||
if (state.indexVariable != null) return null
|
||||
override val indexVariableUsePossible: Boolean
|
||||
get() = true
|
||||
|
||||
override fun match(state: MatchingState): ResultTransformationMatch? {
|
||||
val statement = state.statements.singleOrNull() ?: return null
|
||||
//TODO: it can be implicit 'this' too
|
||||
val qualifiedExpression = statement as? KtDotQualifiedExpression ?: return null
|
||||
@@ -95,16 +95,22 @@ class AddToCollectionTransformation(
|
||||
val argument = callExpression.valueArguments.singleOrNull() ?: return null
|
||||
val argumentValue = argument.getArgumentExpression() ?: return null
|
||||
|
||||
matchWithCollectionInitializationReplacement(state, targetCollection, argumentValue)
|
||||
?.let { return it }
|
||||
if (state.indexVariable == null) {
|
||||
matchWithCollectionInitializationReplacement(state, targetCollection, argumentValue)
|
||||
?.let { return it }
|
||||
}
|
||||
|
||||
val transformation = if (argumentValue.isVariableReference(state.inputVariable)) {
|
||||
AddToCollectionTransformation(state.outerLoop, state.inputVariable, targetCollection)
|
||||
if (state.indexVariable == null && argumentValue.isVariableReference(state.inputVariable)) {
|
||||
return ResultTransformationMatch(AddToCollectionTransformation(state.outerLoop, targetCollection))
|
||||
}
|
||||
else if (state.indexVariable != null) {
|
||||
val mapIndexedTransformation = MapIndexedTransformation(state.outerLoop, state.inputVariable, state.indexVariable, argumentValue)
|
||||
val addToCollectionTransformation = AddToCollectionTransformation(state.outerLoop, targetCollection)
|
||||
return ResultTransformationMatch(addToCollectionTransformation, mapIndexedTransformation)
|
||||
}
|
||||
else {
|
||||
MapToTransformation.create(state.outerLoop, state.inputVariable, targetCollection, argumentValue)
|
||||
return ResultTransformationMatch(MapToTransformation.create(state.outerLoop, state.inputVariable, targetCollection, argumentValue))
|
||||
}
|
||||
return ResultTransformationMatch(transformation)
|
||||
}
|
||||
|
||||
private fun matchWithCollectionInitializationReplacement(
|
||||
|
||||
+3
@@ -71,6 +71,9 @@ class CountTransformation(
|
||||
* }
|
||||
*/
|
||||
object Matcher : ResultTransformationMatcher {
|
||||
override val indexVariableUsePossible: Boolean
|
||||
get() = false
|
||||
|
||||
override fun match(state: MatchingState): ResultTransformationMatch? {
|
||||
val statement = state.statements.singleOrNull() as? KtUnaryExpression ?: return null
|
||||
if (statement.operationToken != KtTokens.PLUSPLUS) return null
|
||||
|
||||
+3
-3
@@ -71,10 +71,10 @@ class FindAndAssignTransformation(
|
||||
* }
|
||||
*/
|
||||
object Matcher : ResultTransformationMatcher {
|
||||
override fun match(state: MatchingState): ResultTransformationMatch? {
|
||||
//TODO: pass indexVariable as null if not used
|
||||
if (state.indexVariable != null) return null
|
||||
override val indexVariableUsePossible: Boolean
|
||||
get() = false
|
||||
|
||||
override fun match(state: MatchingState): ResultTransformationMatch? {
|
||||
when (state.statements.size) {
|
||||
1 -> {}
|
||||
|
||||
|
||||
+3
-3
@@ -70,10 +70,10 @@ class FindAndReturnTransformation(
|
||||
* return ...
|
||||
*/
|
||||
object Matcher : ResultTransformationMatcher {
|
||||
override fun match(state: MatchingState): ResultTransformationMatch? {
|
||||
//TODO: pass indexVariable as null if not used
|
||||
if (state.indexVariable != null) return null
|
||||
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
|
||||
|
||||
+31
-5
@@ -69,22 +69,20 @@ class FilterTransformation(
|
||||
*/
|
||||
object Matcher : SequenceTransformationMatcher {
|
||||
override fun match(state: MatchingState): SequenceTransformationMatch? {
|
||||
if (state.indexVariable != null) return null //TODO?
|
||||
|
||||
val ifStatement = state.statements.firstOrNull() as? KtIfExpression ?: return null
|
||||
if (ifStatement.`else` != null) return null
|
||||
val condition = ifStatement.condition ?: return null
|
||||
val then = ifStatement.then ?: return null
|
||||
|
||||
if (state.statements.size == 1) {
|
||||
val transformation = createFilterTransformation(state.outerLoop, state.inputVariable, condition, isInverse = false)
|
||||
val transformation = createFilterTransformation(state.outerLoop, state.inputVariable, state.indexVariable, condition, isInverse = false)
|
||||
val newState = state.copy(statements = listOf(then))
|
||||
return SequenceTransformationMatch(transformation, newState)
|
||||
}
|
||||
else {
|
||||
val continueExpression = then.blockExpressionsOrSingle().singleOrNull() as? KtContinueExpression ?: return null
|
||||
if (!continueExpression.isBreakOrContinueOfLoop(state.innerLoop)) return null
|
||||
val transformation = createFilterTransformation(state.outerLoop, state.inputVariable, condition, isInverse = true)
|
||||
val transformation = createFilterTransformation(state.outerLoop, state.inputVariable, state.indexVariable, condition, isInverse = true)
|
||||
val newState = state.copy(statements = state.statements.drop(1))
|
||||
return SequenceTransformationMatch(transformation, newState)
|
||||
}
|
||||
@@ -94,11 +92,17 @@ class FilterTransformation(
|
||||
private fun createFilterTransformation(
|
||||
loop: KtForExpression,
|
||||
inputVariable: KtCallableDeclaration,
|
||||
indexVariable: KtCallableDeclaration?,
|
||||
condition: KtExpression,
|
||||
isInverse: Boolean): SequenceTransformation {
|
||||
isInverse: Boolean
|
||||
): SequenceTransformation {
|
||||
|
||||
val effectiveCondition = if (isInverse) condition.negate() else condition
|
||||
|
||||
if (indexVariable != null && indexVariable.hasUsages(condition)) {
|
||||
return FilterIndexedTransformation(loop, inputVariable, indexVariable, effectiveCondition)
|
||||
}
|
||||
|
||||
if (effectiveCondition is KtIsExpression
|
||||
&& !effectiveCondition.isNegated
|
||||
&& effectiveCondition.leftHandSide.isSimpleName(inputVariable.nameAsSafeName) // we cannot use isVariableReference here because expression can be non-physical
|
||||
@@ -150,3 +154,25 @@ class FilterNotNullTransformation(override val loop: KtForExpression) : Sequence
|
||||
return chainedCallGenerator.generate("filterNotNull()")
|
||||
}
|
||||
}
|
||||
|
||||
class FilterIndexedTransformation(
|
||||
override val loop: KtForExpression,
|
||||
val inputVariable: KtCallableDeclaration,
|
||||
val indexVariable: KtCallableDeclaration,
|
||||
val condition: KtExpression
|
||||
) : SequenceTransformation {
|
||||
|
||||
//TODO: how to handle multiple if's using index?
|
||||
|
||||
override val affectsIndex: Boolean
|
||||
get() = true
|
||||
|
||||
override val presentation: String
|
||||
get() = "filterIndexed{}"
|
||||
|
||||
override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression {
|
||||
val lambda = generateLambda(condition, indexVariable, inputVariable)
|
||||
return chainedCallGenerator.generate("filterIndexed $0:'{}'", lambda)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+15
-6
@@ -20,9 +20,7 @@ import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
||||
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.*
|
||||
import org.jetbrains.kotlin.idea.util.FuzzyType
|
||||
import org.jetbrains.kotlin.psi.KtCallableDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.psi.KtForExpression
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
|
||||
class FlatMapTransformation(
|
||||
@@ -53,8 +51,6 @@ class FlatMapTransformation(
|
||||
*/
|
||||
object Matcher : SequenceTransformationMatcher {
|
||||
override fun match(state: MatchingState): SequenceTransformationMatch? {
|
||||
if (state.indexVariable != null) return null
|
||||
|
||||
val nestedLoop = state.statements.singleOrNull() as? KtForExpression ?: return null
|
||||
|
||||
val transform = nestedLoop.loopRange ?: return null
|
||||
@@ -65,8 +61,21 @@ class FlatMapTransformation(
|
||||
if (iterableType.checkIsSuperTypeOf(nestedSequenceType) == null) return null
|
||||
|
||||
val nestedLoopBody = nestedLoop.body ?: return null
|
||||
|
||||
val newWorkingVariable = nestedLoop.loopParameter ?: return null
|
||||
|
||||
if (state.indexVariable != null && state.indexVariable.hasUsages(transform)) {
|
||||
// if nested loop range uses index, convert to "mapIndexed {...}.flatMap { it }"
|
||||
val mapIndexedTransformation = MapIndexedTransformation(state.outerLoop, state.inputVariable, state.indexVariable, transform)
|
||||
val inputVarExpression = KtPsiFactory(nestedLoop).createExpressionByPattern("$0", state.inputVariable.nameAsSafeName)
|
||||
val flatMapTransformation = FlatMapTransformation(state.outerLoop, state.inputVariable, inputVarExpression)
|
||||
val newState = state.copy(
|
||||
innerLoop = nestedLoop,
|
||||
statements = listOf(nestedLoopBody),
|
||||
inputVariable = newWorkingVariable
|
||||
)
|
||||
return SequenceTransformationMatch(listOf(mapIndexedTransformation, flatMapTransformation), newState)
|
||||
}
|
||||
|
||||
val transformation = FlatMapTransformation(state.outerLoop, state.inputVariable, transform)
|
||||
val newState = state.copy(
|
||||
innerLoop = nestedLoop,
|
||||
|
||||
+24
-4
@@ -48,16 +48,36 @@ class MapTransformation(
|
||||
*/
|
||||
object Matcher : SequenceTransformationMatcher {
|
||||
override fun match(state: MatchingState): SequenceTransformationMatch? {
|
||||
if (state.indexVariable != null) return null //TODO?
|
||||
|
||||
val declaration = state.statements.firstOrNull() as? KtProperty ?: return null //TODO: support multi-variables
|
||||
val initializer = declaration.initializer ?: return null
|
||||
if (declaration.hasWriteUsages()) return null
|
||||
val restStatements = state.statements.drop(1)
|
||||
|
||||
val transformation = MapTransformation(state.outerLoop, state.inputVariable, initializer)
|
||||
val transformation = if (state.indexVariable != null && state.indexVariable.hasUsages(initializer))
|
||||
MapIndexedTransformation(state.outerLoop, state.inputVariable, state.indexVariable, initializer)
|
||||
else
|
||||
MapTransformation(state.outerLoop, state.inputVariable, initializer)
|
||||
val newState = state.copy(statements = restStatements, inputVariable = declaration)
|
||||
return SequenceTransformationMatch(transformation, newState)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MapIndexedTransformation(
|
||||
override val loop: KtForExpression,
|
||||
val inputVariable: KtCallableDeclaration,
|
||||
val indexVariable: KtCallableDeclaration,
|
||||
val mapping: KtExpression
|
||||
) : SequenceTransformation {
|
||||
|
||||
override val affectsIndex: Boolean
|
||||
get() = false
|
||||
|
||||
override val presentation: String
|
||||
get() = "mapIndexed{}"
|
||||
|
||||
override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression {
|
||||
val lambda = generateLambda(mapping, indexVariable, inputVariable)
|
||||
return chainedCallGenerator.generate("mapIndexed $0:'{}'", lambda)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +81,25 @@ fun generateLambda(inputVariable: KtCallableDeclaration, expression: KtExpressio
|
||||
return psiFactory.createExpressionByPattern("{ $0 }", lambdaExpression.bodyExpression!!) as KtLambdaExpression
|
||||
}
|
||||
|
||||
fun generateLambda(expression: KtExpression, vararg inputVariables: KtCallableDeclaration): KtLambdaExpression {
|
||||
return KtPsiFactory(expression).buildExpression {
|
||||
appendFixedText("{")
|
||||
|
||||
for ((index, variable) in inputVariables.withIndex()) {
|
||||
if (index > 0) {
|
||||
appendFixedText(",")
|
||||
}
|
||||
appendName(variable.nameAsSafeName)
|
||||
}
|
||||
|
||||
appendFixedText("->")
|
||||
|
||||
appendExpression(expression)
|
||||
|
||||
appendFixedText("}")
|
||||
} as KtLambdaExpression
|
||||
}
|
||||
|
||||
fun KtExpression?.isTrueConstant()
|
||||
= this != null && node?.elementType == KtNodeTypes.BOOLEAN_CONSTANT && text == "true"
|
||||
|
||||
@@ -100,12 +119,14 @@ fun KtCallableDeclaration.hasUsages(inElement: KtElement): Boolean {
|
||||
}
|
||||
|
||||
fun KtCallableDeclaration.hasUsages(inElements: Collection<KtElement>): Boolean {
|
||||
assert(this.isPhysical)
|
||||
// TODO: it's a temporary workaround about strange dead-lock when running inspections
|
||||
return inElements.any { ReferencesSearch.search(this, LocalSearchScope(it)).any() }
|
||||
// return ReferencesSearch.search(this, LocalSearchScope(inElements.toTypedArray())).any()
|
||||
}
|
||||
|
||||
fun KtProperty.hasWriteUsages(): Boolean {
|
||||
assert(this.isPhysical)
|
||||
if (!isVar) return false
|
||||
return ReferencesSearch.search(this, useScope).any {
|
||||
(it as? KtSimpleNameReference)?.element?.readWriteAccess(useResolveForReadWrite = true)?.isWrite == true
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// WITH_RUNTIME
|
||||
// INTENTION_TEXT: "Replace with 'filterIndexed{}.any()'"
|
||||
fun foo(list: List<String>) {
|
||||
var found = false
|
||||
<caret>for ((index, s) in list.withIndex()) {
|
||||
if (s.length > index) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// WITH_RUNTIME
|
||||
// INTENTION_TEXT: "Replace with 'filterIndexed{}.any()'"
|
||||
fun foo(list: List<String>) {
|
||||
val <caret>found = list
|
||||
.filterIndexed { index, s -> s.length > index }
|
||||
.any()
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// WITH_RUNTIME
|
||||
// INTENTION_TEXT: "Replace with 'filterIndexed{}.firstOrNull()'"
|
||||
fun foo(list: List<String>): String? {
|
||||
<caret>for ((index, s) in list.withIndex()) {
|
||||
if (s.length > index) {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// WITH_RUNTIME
|
||||
// INTENTION_TEXT: "Replace with 'filterIndexed{}.firstOrNull()'"
|
||||
fun foo(list: List<String>): String? {
|
||||
<caret>return list
|
||||
.filterIndexed { index, s -> s.length > index }
|
||||
.firstOrNull()
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// WITH_RUNTIME
|
||||
// INTENTION_TEXT: "Replace with 'mapIndexed{}.flatMap{}.firstOrNull{}'"
|
||||
fun foo(list: List<String>): String? {
|
||||
<caret>for ((index, s) in list.withIndex()) {
|
||||
for (line in s.lines().take(index)) {
|
||||
if (line.isNotBlank()) return line
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// WITH_RUNTIME
|
||||
// INTENTION_TEXT: "Replace with 'mapIndexed{}.flatMap{}.firstOrNull{}'"
|
||||
fun foo(list: List<String>): String? {
|
||||
<caret>return list
|
||||
.mapIndexed { index, s -> s.lines().take(index) }
|
||||
.flatMap { it }
|
||||
.firstOrNull { it.isNotBlank() }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// WITH_RUNTIME
|
||||
// INTENTION_TEXT: "Replace with 'map{}.filterIndexed{}.firstOrNull()'"
|
||||
fun foo(list: List<String>): Int? {
|
||||
<caret>for ((index, s) in list.withIndex()) {
|
||||
val l = s.length
|
||||
if (l > index) {
|
||||
return l
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// WITH_RUNTIME
|
||||
// INTENTION_TEXT: "Replace with 'map{}.filterIndexed{}.firstOrNull()'"
|
||||
fun foo(list: List<String>): Int? {
|
||||
<caret>return list
|
||||
.map { it.length }
|
||||
.filterIndexed { index, l -> l > index }
|
||||
.firstOrNull()
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// WITH_RUNTIME
|
||||
// INTENTION_TEXT: "Replace with 'mapIndexed{}.firstOrNull{}'"
|
||||
fun foo(list: List<String>): Int? {
|
||||
<caret>for ((index, s) in list.withIndex()) {
|
||||
val x = s.length * index
|
||||
if (x > 0) return x
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// WITH_RUNTIME
|
||||
// INTENTION_TEXT: "Replace with 'mapIndexed{}.firstOrNull{}'"
|
||||
fun foo(list: List<String>): Int? {
|
||||
<caret>return list
|
||||
.mapIndexed { index, s -> s.length * index }
|
||||
.firstOrNull { it > 0 }
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// WITH_RUNTIME
|
||||
// INTENTION_TEXT: "Replace with 'mapIndexed{}.mapIndexed{}.firstOrNull{}'"
|
||||
fun foo(list: List<String>): Int? {
|
||||
<caret>for ((index, s) in list.withIndex()) {
|
||||
val x = s.length * index
|
||||
val y = x + index
|
||||
if (y > 0) return y
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// WITH_RUNTIME
|
||||
// INTENTION_TEXT: "Replace with 'mapIndexed{}.mapIndexed{}.firstOrNull{}'"
|
||||
fun foo(list: List<String>): Int? {
|
||||
<caret>return list
|
||||
.mapIndexed { index, s -> s.length * index }
|
||||
.mapIndexed { index, x -> x + index }
|
||||
.firstOrNull { it > 0 }
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// WITH_RUNTIME
|
||||
// INTENTION_TEXT: "Replace with '+= mapIndexed{}'"
|
||||
fun foo(list: List<String>, target: MutableList<Int>) {
|
||||
<caret>for ((index, s) in list.withIndex()) {
|
||||
target.add(s.hashCode() * index)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// WITH_RUNTIME
|
||||
// INTENTION_TEXT: "Replace with '+= mapIndexed{}'"
|
||||
fun foo(list: List<String>, target: MutableList<Int>) {
|
||||
<caret>target += list.mapIndexed { index, s -> s.hashCode() * index }
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// WITH_RUNTIME
|
||||
// IS_APPLICABLE: false
|
||||
fun foo(list: List<String>): Int? {
|
||||
<caret>for ((index, s) in list.withIndex()) {
|
||||
if (s.isBlank()) continue
|
||||
val x = s.length * index
|
||||
if (x > 1000) return x
|
||||
}
|
||||
return null
|
||||
}
|
||||
Reference in New Issue
Block a user