diff --git a/.idea/inspectionProfiles/Loop_to_call_chain.xml b/.idea/inspectionProfiles/Loop_to_call_chain.xml new file mode 100644 index 00000000000..3d5a104dcb6 --- /dev/null +++ b/.idea/inspectionProfiles/Loop_to_call_chain.xml @@ -0,0 +1,2133 @@ + + + + \ No newline at end of file diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtForExpression.java b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtForExpression.java index 49e66113b29..04648c79759 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtForExpression.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtForExpression.java @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.psi; import com.intellij.lang.ASTNode; +import com.intellij.psi.PsiElement; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.KtNodeTypes; @@ -48,7 +49,12 @@ public class KtForExpression extends KtLoopExpression { } @Nullable @IfNotParsed - public ASTNode getInKeywordNode() { - return getNode().findChildByType(KtTokens.IN_KEYWORD); + public PsiElement getInKeyword() { + return findChildByType(KtTokens.IN_KEYWORD); + } + + @NotNull + public PsiElement getForKeyword() { + return findChildByType(KtTokens.FOR_KEYWORD); } } diff --git a/core/descriptors/src/org/jetbrains/kotlin/builtins/KotlinBuiltIns.java b/core/descriptors/src/org/jetbrains/kotlin/builtins/KotlinBuiltIns.java index 6bf34144d20..8bc8054c510 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/builtins/KotlinBuiltIns.java +++ b/core/descriptors/src/org/jetbrains/kotlin/builtins/KotlinBuiltIns.java @@ -691,6 +691,11 @@ public abstract class KotlinBuiltIns { return getString().getDefaultType(); } + @NotNull + public KotlinType getIterableType() { + return getIterable().getDefaultType(); + } + @NotNull public KotlinType getArrayElementType(@NotNull KotlinType arrayType) { if (isArray(arrayType)) { diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/inspections/IntentionBasedInspection.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/inspections/IntentionBasedInspection.kt index f8df4c2c88a..583e0946615 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/inspections/IntentionBasedInspection.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/inspections/IntentionBasedInspection.kt @@ -37,18 +37,22 @@ abstract class IntentionBasedInspection( ) : AbstractKotlinInspection() { constructor( - intention: SelfTargetingRangeIntention - ) : this(listOf(IntentionData(intention)), null, intention.elementType) + intention: SelfTargetingRangeIntention, + problemText: String? = null + ) : this(listOf(IntentionData(intention)), problemText, intention.elementType) constructor( intention: SelfTargetingRangeIntention, - additionalChecker: (TElement, IntentionBasedInspection) -> Boolean - ) : this(listOf(IntentionData(intention, additionalChecker)), null, intention.elementType) + additionalChecker: (TElement, IntentionBasedInspection) -> Boolean, + problemText: String? = null + ) : this(listOf(IntentionData(intention, additionalChecker)), problemText, intention.elementType) constructor( intention: SelfTargetingRangeIntention, - additionalChecker: (TElement) -> Boolean - ) : this(listOf(IntentionData(intention, { element, inspection -> additionalChecker(element) } )), null, intention.elementType) + additionalChecker: (TElement) -> Boolean, + problemText: String? = null + ) : this(listOf(IntentionData(intention, { element, inspection -> additionalChecker(element) } )), problemText, intention.elementType) + data class IntentionData( diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/KtForLoopInReference.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/KtForLoopInReference.kt index 11f8dc42fba..fca1dc5c756 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/KtForLoopInReference.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/KtForLoopInReference.kt @@ -25,12 +25,12 @@ import java.util.* class KtForLoopInReference(element: KtForExpression) : KtMultiReference(element) { override fun getRangeInElement(): TextRange { - val inKeywordNode = expression.inKeywordNode - if (inKeywordNode == null) + val inKeyword = expression.inKeyword + if (inKeyword == null) return TextRange.EMPTY_RANGE - val offset = inKeywordNode.psi!!.startOffsetInParent - return TextRange(offset, offset + inKeywordNode.textLength) + val offset = inKeyword.startOffsetInParent + return TextRange(offset, offset + inKeyword.textLength) } override fun getTargetDescriptors(context: BindingContext): Collection { diff --git a/idea/resources/inspectionDescriptions/LoopToCallChain.html b/idea/resources/inspectionDescriptions/LoopToCallChain.html new file mode 100644 index 00000000000..a733fbb15e2 --- /dev/null +++ b/idea/resources/inspectionDescriptions/LoopToCallChain.html @@ -0,0 +1,5 @@ + + +This inspection reports for-loops that can be replaced with a sequence of stdlib-operations (like "map", "filter" etc) + + diff --git a/idea/resources/intentionDescriptions/LoopToCallChainIntention/after.kt.template b/idea/resources/intentionDescriptions/LoopToCallChainIntention/after.kt.template new file mode 100644 index 00000000000..01134af2f4e --- /dev/null +++ b/idea/resources/intentionDescriptions/LoopToCallChainIntention/after.kt.template @@ -0,0 +1,3 @@ +fun foo(list: List): String? { + return list.firstOrNull { s -> s.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 new file mode 100644 index 00000000000..46811f931d8 --- /dev/null +++ b/idea/resources/intentionDescriptions/LoopToCallChainIntention/before.kt.template @@ -0,0 +1,8 @@ +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 new file mode 100644 index 00000000000..bb27c3fd502 --- /dev/null +++ b/idea/resources/intentionDescriptions/LoopToCallChainIntention/description.html @@ -0,0 +1,5 @@ + + +This intention converts a for-loop into a sequence of stdlib-operations (like "map", "filter" etc) + + diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index cbb88e978e9..3b5012f385a 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -1013,6 +1013,11 @@ Kotlin + + org.jetbrains.kotlin.idea.intentions.loopToCallChain.LoopToCallChainIntention + Kotlin + + org.jetbrains.kotlin.idea.intentions.SwapBinaryExpressionIntention Kotlin @@ -1507,6 +1512,13 @@ language="kotlin" /> + + ( - listOf(IntentionBasedInspection.IntentionData(RemoveForLoopIndicesIntention())), - "Index is not used in the loop body", - KtForExpression::class.java + RemoveForLoopIndicesIntention(), + problemText = "Index is not used in the loop body" ) { override val problemHighlightType: ProblemHighlightType get() = ProblemHighlightType.LIKE_UNUSED_SYMBOL diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/LoopToCallChainIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/LoopToCallChainIntention.kt new file mode 100644 index 00000000000..16759cb517b --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/LoopToCallChainIntention.kt @@ -0,0 +1,44 @@ +/* + * 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.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.psi.KtForExpression +import org.jetbrains.kotlin.psi.psiUtil.startOffset + +class LoopToCallChainInspection : IntentionBasedInspection( + LoopToCallChainIntention(), + problemText = "Loop can be replaced with stdlib operations") + +class LoopToCallChainIntention : SelfTargetingRangeIntention( + KtForExpression::class.java, + "Replace with stdlib operations" +) { + override fun applicabilityRange(element: KtForExpression): TextRange? { + return if (match(element) != null) element.forKeyword.textRange else null + } + + override fun applyTo(element: KtForExpression, editor: Editor?) { + val match = match(element)!! + val result = convertLoop(element, match) + editor?.moveCaret(result.startOffset) + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/interfaces.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/interfaces.kt new file mode 100644 index 00000000000..7817206b317 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/interfaces.kt @@ -0,0 +1,85 @@ +/* + * 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 org.jetbrains.kotlin.psi.KtCallableDeclaration +import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.psi.KtForExpression +import org.jetbrains.kotlin.psi.psiUtil.PsiChildRange + +interface ChainedCallGenerator { + fun generate(pattern: String, vararg args: Any): KtExpression +} + +interface Transformation { + val inputVariable: KtCallableDeclaration +} + +interface SequenceTransformation : Transformation { + val affectsIndex: Boolean + + fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression +} + +interface ResultTransformation : Transformation { + val canIncludeFilter: Boolean + val canIncludeMap: Boolean + + val commentSavingRange: PsiChildRange + val commentRestoringRange: PsiChildRange + + fun generateCode( + chainedCallGenerator: ChainedCallGenerator, + filter: FilterOrMap?, + map: FilterOrMap? + ): KtExpression + + fun convertLoop(resultCallChain: KtExpression): KtExpression +} + +data class FilterOrMap(val expression: KtExpression, val workingVariable: KtCallableDeclaration) + +data class MatchingState( + val statements: Collection, + val workingVariable: KtCallableDeclaration, + val indexVariable: KtCallableDeclaration?, + val loop: KtForExpression +) + +interface SequenceTransformationMatcher { + fun match(state: MatchingState): SequenceTransformationMatch? +} + +class SequenceTransformationMatch( + val transformations: Collection, + val newState: MatchingState +) { + init { + assert(transformations.isNotEmpty()) + } + + constructor(transformation: SequenceTransformation, newState: MatchingState) : this(listOf(transformation), newState) +} + +interface ResultTransformationMatcher { + fun match(state: MatchingState): ResultTransformationMatch? +} + +class ResultTransformationMatch( + val resultTransformation: ResultTransformation, + val sequenceTransformations: Collection = listOf() +) diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/matchAndConvert.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/matchAndConvert.kt new file mode 100644 index 00000000000..3639f41a027 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/matchAndConvert.kt @@ -0,0 +1,124 @@ +/* + * 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 org.jetbrains.kotlin.idea.intentions.loopToCallChain.result.FindAndAssignTransformation +import org.jetbrains.kotlin.idea.intentions.loopToCallChain.result.FindAndReturnTransformation +import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.FilterTransformation +import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.FlatMapTransformation +import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.MapTransformation +import org.jetbrains.kotlin.idea.intentions.negate +import org.jetbrains.kotlin.idea.util.CommentSaver +import org.jetbrains.kotlin.psi.* +import java.util.* + +object MatcherRegistrar { + val sequenceMatchers: Collection = listOf( + FilterTransformation.Matcher, + MapTransformation.Matcher, + FlatMapTransformation.Matcher + ) + + val resultMatchers: Collection = listOf( + FindAndReturnTransformation.Matcher, + FindAndAssignTransformation.Matcher + ) +} + +fun match(loop: KtForExpression): ResultTransformationMatch? { + val sequenceTransformations = ArrayList() + var state = MatchingState( + statements = listOf(loop.body ?: return null), + workingVariable = loop.loopParameter ?: return null, + indexVariable = null, + loop = loop + ) + + MatchLoop@ + while (true) { + val block = state.statements.singleOrNull() as? KtBlockExpression + if (block != null) { + state = state.copy(statements = block.statements) + } + + for (matcher in MatcherRegistrar.resultMatchers) { + val match = matcher.match(state) + if (match != null) { + sequenceTransformations.addAll(match.sequenceTransformations) + return ResultTransformationMatch(match.resultTransformation, sequenceTransformations) + } + } + + for (matcher in MatcherRegistrar.sequenceMatchers) { + val match = matcher.match(state) + if (match != null) { + sequenceTransformations.addAll(match.transformations) + state = match.newState + continue@MatchLoop + } + } + + return null + } +} + +//TODO: offer to use of .asSequence() as an option +fun convertLoop(loop: KtForExpression, matchResult: ResultTransformationMatch): KtExpression { + val commentSaver = CommentSaver(matchResult.resultTransformation.commentSavingRange) + + var callChain = matchResult.generateCallChain(loop) + + val result = matchResult.resultTransformation.convertLoop(callChain) + + commentSaver.restore(matchResult.resultTransformation.commentRestoringRange) + + return result +} + +private fun ResultTransformationMatch.generateCallChain(loop: KtForExpression): KtExpression { + var sequenceTransformations = sequenceTransformations + val last = sequenceTransformations.lastOrNull() + var lastFilter: FilterOrMap? = null + var lastMap: FilterOrMap? = null //TODO + if (last is FilterTransformation && resultTransformation.canIncludeFilter) { + val condition = if (last.isInverse) last.condition.negate() else last.condition + lastFilter = FilterOrMap(condition, last.inputVariable) + sequenceTransformations = sequenceTransformations.take(sequenceTransformations.size - 1) + } + + val lineBreak = if (sequenceTransformations.isNotEmpty()) "\n" else "" + + var callChain = loop.loopRange!! + + val psiFactory = KtPsiFactory(loop) + fun chainedCallGenerator(): ChainedCallGenerator { + return object : ChainedCallGenerator { + override fun generate(pattern: String, vararg args: Any): KtExpression { + val newPattern = "\$${args.size}$lineBreak.$pattern" + return psiFactory.createExpressionByPattern(newPattern, *args, callChain) + } + } + } + + for (transformation in sequenceTransformations) { + callChain = transformation.generateCode(chainedCallGenerator()) + } + + callChain = resultTransformation.generateCode(chainedCallGenerator(), lastFilter, lastMap) + return callChain +} + diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/FindAndAssignTransformation.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/FindAndAssignTransformation.kt new file mode 100644 index 00000000000..98fc162c61d --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/FindAndAssignTransformation.kt @@ -0,0 +1,92 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.intentions.loopToCallChain.result + +import com.intellij.psi.search.LocalSearchScope +import com.intellij.psi.search.searches.ReferencesSearch +import org.jetbrains.kotlin.idea.intentions.loopToCallChain.* +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.PsiChildRange + +class FindAndAssignTransformation( + private val loop: KtForExpression, + override val inputVariable: KtCallableDeclaration, + private val stdlibFunName: String, + private val initialDeclaration: KtProperty +) : ResultTransformation { + + override val commentSavingRange = PsiChildRange(initialDeclaration, loop.unwrapIfLabeled()) + override val commentRestoringRange = commentSavingRange.withoutLastStatement() + + override val canIncludeFilter: Boolean + get() = true + + override val canIncludeMap: Boolean + get() = false + + override fun generateCode(chainedCallGenerator: ChainedCallGenerator, filter: FilterOrMap?, map: FilterOrMap?): KtExpression { + return if (filter == null) { + chainedCallGenerator.generate("$stdlibFunName()") + } + else { + val lambda = generateLambda(filter.workingVariable, filter.expression) + chainedCallGenerator.generate("$stdlibFunName $0:'{}'", lambda) + } + } + + override fun convertLoop(resultCallChain: KtExpression): KtExpression { + initialDeclaration.initializer!!.replace(resultCallChain) + loop.deleteWithLabels() + + if (!initialDeclaration.hasWriteUsages()) { // change variable to 'val' if possible + initialDeclaration.valOrVarKeyword.replace(KtPsiFactory(initialDeclaration).createValKeyword()) + } + + return initialDeclaration + } + + object Matcher : ResultTransformationMatcher { + override fun match(state: MatchingState): ResultTransformationMatch? { + //TODO: pass indexVariable as null if not used + if (state.indexVariable != null) return null + + if (state.statements.size != 2) return null + + val breakExpression = state.statements.last() as? KtBreakExpression ?: return null + if (!breakExpression.isBreakOrContinueOfLoop(state.loop)) return null + + 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 + + //TODO: support also assignment instead of declaration + val declarationBeforeLoop = state.loop.previousStatement() as? KtProperty ?: return null + val initializer = declarationBeforeLoop.initializer ?: return null + if (!left.isVariableReference(declarationBeforeLoop)) return null + + val usageCountInLoop = ReferencesSearch.search(declarationBeforeLoop, LocalSearchScope(state.loop)).count() + if (usageCountInLoop != 1) return null // this should be the only usage of this variable inside the loop + + val stdlibFunName = stdlibFunNameForFind(right, initializer, state.workingVariable) ?: return null + + val transformation = FindAndAssignTransformation(state.loop, state.workingVariable, stdlibFunName, declarationBeforeLoop) + return ResultTransformationMatch(transformation) + } + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/FindAndReturnTransformation.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/FindAndReturnTransformation.kt new file mode 100644 index 00000000000..7dddf6a1af8 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/FindAndReturnTransformation.kt @@ -0,0 +1,77 @@ +/* + * 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.psi.KtCallableDeclaration +import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.psi.KtForExpression +import org.jetbrains.kotlin.psi.KtReturnExpression +import org.jetbrains.kotlin.psi.psiUtil.PsiChildRange + +class FindAndReturnTransformation( + private val loop: KtForExpression, + override val inputVariable: KtCallableDeclaration, + private val stdlibFunName: String, + private val endReturn: KtReturnExpression +) : ResultTransformation { + + override val commentSavingRange = PsiChildRange(loop.unwrapIfLabeled(), endReturn) + + override val commentRestoringRange = commentSavingRange.withoutFirstStatement() + + override val canIncludeFilter: Boolean + get() = true + + override val canIncludeMap: Boolean + get() = false + + override fun generateCode(chainedCallGenerator: ChainedCallGenerator, filter: FilterOrMap?, map: FilterOrMap?): KtExpression { + return if (filter == null) { + chainedCallGenerator.generate("$stdlibFunName()") + } + else { + val lambda = generateLambda(filter.workingVariable, filter.expression) + chainedCallGenerator.generate("$stdlibFunName $0:'{}'", lambda) + } + } + + override fun convertLoop(resultCallChain: KtExpression): KtExpression { + endReturn.returnedExpression!!.replace(resultCallChain) + loop.deleteWithLabels() + return endReturn + } + + object Matcher : ResultTransformationMatcher { + override fun match(state: MatchingState): ResultTransformationMatch? { + //TODO: pass indexVariable as null if not used + if (state.indexVariable != null) return null + + val returnInLoop = state.statements.singleOrNull() as? KtReturnExpression ?: return null + val returnAfterLoop = state.loop.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 stdlibFunName = stdlibFunNameForFind(returnValueInLoop, returnValueAfterLoop, state.workingVariable) ?: return null + + val transformation = FindAndReturnTransformation(state.loop, state.workingVariable, stdlibFunName, returnAfterLoop) + return ResultTransformationMatch(transformation) + } + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/sequence/FilterTransformation.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/sequence/FilterTransformation.kt new file mode 100644 index 00000000000..051f6fe8ee0 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/sequence/FilterTransformation.kt @@ -0,0 +1,123 @@ +/* + * 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.sequence + +import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isNullExpression +import org.jetbrains.kotlin.idea.intentions.loopToCallChain.* +import org.jetbrains.kotlin.idea.intentions.negate +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.blockExpressionsOrSingle + +class FilterTransformation( + override val inputVariable: KtCallableDeclaration, + val condition: KtExpression, + val isInverse: Boolean +) : SequenceTransformation { + + init { + assert(condition.isPhysical) + } + + override val affectsIndex: Boolean + get() = true + + override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression { + val lambda = generateLambda(inputVariable, condition) + val name = if (isInverse) "filterNot" else "filter" + return chainedCallGenerator.generate("$0$1:'{}'", name, lambda) + } + + //TODO: merge subsequent filters + 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.workingVariable, 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.loop)) return null + val transformation = createFilterTransformation(state.workingVariable, condition, isInverse = true) + val newState = state.copy(statements = state.statements.drop(1)) + return SequenceTransformationMatch(transformation, newState) + } + } + + //TODO: choose filter or filterNot depending on condition + private fun createFilterTransformation( + inputVariable: KtCallableDeclaration, + condition: KtExpression, + isInverse: Boolean): SequenceTransformation { + + val realCondition = if (isInverse) condition.negate() else condition + + if (realCondition is KtIsExpression + && !realCondition.isNegated + && realCondition.leftHandSide.isSimpleName(inputVariable.nameAsSafeName) // we cannot use isVariableReference here because expression can be non-physical + ) { + val typeRef = realCondition.typeReference + if (typeRef != null) { + return FilterIsInstanceTransformation(inputVariable, typeRef) + } + } + + if (realCondition is KtBinaryExpression + && realCondition.operationToken == KtTokens.EXCLEQ + && realCondition.right.isNullExpression() + && realCondition.left.isSimpleName(inputVariable.nameAsSafeName) + ) { + return FilterNotNullTransformation(inputVariable) + } + + return FilterTransformation(inputVariable, condition, isInverse) + } + } +} + +class FilterIsInstanceTransformation( + override val inputVariable: KtCallableDeclaration, + private val type: KtTypeReference +) : SequenceTransformation { + + override val affectsIndex: Boolean + get() = true + + override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression { + return chainedCallGenerator.generate("filterIsInstance<$0>()", type) + } +} + +class FilterNotNullTransformation( + override val inputVariable: KtCallableDeclaration +) : SequenceTransformation { + + override val affectsIndex: Boolean + get() = true + + override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression { + return chainedCallGenerator.generate("filterNotNull()") + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/sequence/FlatMapTransformation.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/sequence/FlatMapTransformation.kt new file mode 100644 index 00000000000..4254038345c --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/sequence/FlatMapTransformation.kt @@ -0,0 +1,68 @@ +/* + * 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.sequence + +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.resolve.lazy.BodyResolveMode + +class FlatMapTransformation( + override val inputVariable: KtCallableDeclaration, + private val transform: KtExpression +) : SequenceTransformation { + + init { + assert(transform.isPhysical) + } + + override val affectsIndex: Boolean + get() = true + + override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression { + val lambda = generateLambda(inputVariable, transform) + return chainedCallGenerator.generate("flatMap$0:'{}'", lambda) + } + + 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 + // check that we iterate over Iterable + val nestedSequenceType = transform.analyze(BodyResolveMode.PARTIAL).getType(transform) ?: return null + val builtIns = transform.getResolutionFacade().moduleDescriptor.builtIns + val iterableType = FuzzyType(builtIns.iterableType, builtIns.iterable.declaredTypeParameters) + if (iterableType.checkIsSuperTypeOf(nestedSequenceType) == null) return null + + val newWorkingVariable = nestedLoop.loopParameter ?: return null + val loopBody = nestedLoop.body ?: return null + val transformation = FlatMapTransformation(state.workingVariable, transform) + val newState = state.copy( + statements = listOf(loopBody), + workingVariable = newWorkingVariable + ) + return SequenceTransformationMatch(transformation, newState) + } + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/sequence/MapTransformation.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/sequence/MapTransformation.kt new file mode 100644 index 00000000000..6e73f5cc1ca --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/sequence/MapTransformation.kt @@ -0,0 +1,56 @@ +/* + * 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.sequence + +import org.jetbrains.kotlin.idea.intentions.loopToCallChain.* +import org.jetbrains.kotlin.psi.KtCallableDeclaration +import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.psi.KtProperty + +class MapTransformation( + override val inputVariable: KtCallableDeclaration, + val mapping: KtExpression +) : SequenceTransformation { + + init { + assert(mapping.isPhysical) + } + + override val affectsIndex: Boolean + get() = false + + override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression { + val lambda = generateLambda(inputVariable, mapping) + return chainedCallGenerator.generate("map$0:'{}'", lambda) + } + + 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) + if (state.workingVariable.hasUsages(restStatements)) return null // workingVariable is still needed - cannot transform to map() + + val transformation = MapTransformation(state.workingVariable, initializer) + val newState = state.copy(statements = restStatements, workingVariable = declaration) + return SequenceTransformationMatch(transformation, newState) + } + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/utils.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/utils.kt new file mode 100644 index 00000000000..528e012b3c1 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/utils.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.psi.PsiWhiteSpace +import com.intellij.psi.search.LocalSearchScope +import com.intellij.psi.search.searches.ReferencesSearch +import org.jetbrains.kotlin.KtNodeTypes +import org.jetbrains.kotlin.idea.analysis.analyzeInContext +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade +import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isNullExpression +import org.jetbrains.kotlin.idea.references.KtSimpleNameReference +import org.jetbrains.kotlin.idea.references.mainReference +import org.jetbrains.kotlin.idea.references.readWriteAccess +import org.jetbrains.kotlin.idea.util.getResolutionScope +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.* +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode +import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance +import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull + +fun generateLambda(workingVariable: KtCallableDeclaration, expression: KtExpression): KtLambdaExpression { + val psiFactory = KtPsiFactory(expression) + + val lambdaExpression = psiFactory.createExpressionByPattern("{ $0 -> $1 }", workingVariable.nameAsSafeName, expression) as KtLambdaExpression + + val isItUsedInside = expression.anyDescendantOfType { + it.getQualifiedExpressionForSelector() == null && it.getReferencedName() == "it" + } + + if (isItUsedInside) return lambdaExpression + + val resolutionScope = workingVariable.getResolutionScope(workingVariable.analyze(BodyResolveMode.FULL), workingVariable.getResolutionFacade()) + val bindingContext = lambdaExpression.analyzeInContext(resolutionScope, contextExpression = workingVariable) + val lambdaParam = lambdaExpression.valueParameters.single() + val lambdaParamDescriptor = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, lambdaParam] + val usages = lambdaExpression.collectDescendantsOfType { + it.mainReference.resolveToDescriptors(bindingContext).singleOrNull() == lambdaParamDescriptor + } + + val itExpr = psiFactory.createSimpleName("it") + for (usage in usages) { + usage.replace(itExpr) + } + + return psiFactory.createExpressionByPattern("{ $0 }", lambdaExpression.bodyExpression!!) as KtLambdaExpression +} + +fun KtExpression?.isTrueConstant() + = this != null && node?.elementType == KtNodeTypes.BOOLEAN_CONSTANT && text == "true" + +fun KtExpression?.isFalseConstant() + = this != null && node?.elementType == KtNodeTypes.BOOLEAN_CONSTANT && text == "false" + +fun KtExpression?.isVariableReference(variable: KtCallableDeclaration): Boolean { + return this is KtNameReferenceExpression && this.mainReference.isReferenceTo(variable) +} + +fun KtExpression?.isSimpleName(name: Name): Boolean { + return this is KtNameReferenceExpression && this.getQualifiedExpressionForSelector() == null && this.getReferencedNameAsName() == name +} + +fun KtCallableDeclaration.hasUsages(inElements: Collection): Boolean { + return ReferencesSearch.search(this, LocalSearchScope(inElements.toTypedArray())).any() +} + +fun KtProperty.hasWriteUsages(): Boolean { + if (!isVar) return false + return ReferencesSearch.search(this, useScope).any { + (it as? KtSimpleNameReference)?.element?.readWriteAccess(useResolveForReadWrite = true)?.isWrite == true + } +} + +fun stdlibFunNameForFind(valueIfFound: KtExpression, valueIfNotFound: KtExpression, workingVariable: KtCallableDeclaration): String? { + return when { + valueIfNotFound.isNullExpression() && valueIfFound.isVariableReference(workingVariable) -> "firstOrNull" + + valueIfFound.isTrueConstant() && valueIfNotFound.isFalseConstant() -> "any" + + valueIfFound.isFalseConstant() && valueIfNotFound.isTrueConstant() -> "none" + + else -> /*TODO: allow other constants*/ null + } +} + +fun KtExpressionWithLabel.isBreakOrContinueOfLoop(loop: KtLoopExpression): Boolean { + val label = getTargetLabel() + if (label == null) { + val closestLoop = parents.firstIsInstance() + return loop == closestLoop + } + else { + //TODO: does PARTIAL always work here? + val targetLoop = analyze(BodyResolveMode.PARTIAL)[BindingContext.LABEL_TARGET, label] + return targetLoop == loop + } +} + +fun KtExpression.previousStatement(): KtExpression? { + var statement = unwrapIfLabeled() + if (statement.parent !is KtBlockExpression) return null + return statement.siblings(forward = false, withItself = false).firstIsInstanceOrNull() +} + +fun KtExpression.nextStatement(): KtExpression? { + var statement = unwrapIfLabeled() + if (statement.parent !is KtBlockExpression) return null + return statement.siblings(forward = true, withItself = false).firstIsInstanceOrNull() +} + +fun KtExpression.unwrapIfLabeled(): KtExpression { + var statement = this + while (true) { + statement = statement.parent as? KtLabeledExpression ?: return statement + } +} + +fun KtLoopExpression.deleteWithLabels() { + unwrapIfLabeled().delete() +} + +fun PsiChildRange.withoutFirstStatement(): PsiChildRange { + val newFirst = first!!.siblings(forward = true, withItself = false).first { it !is PsiWhiteSpace } + return PsiChildRange(newFirst, last) +} + +fun PsiChildRange.withoutLastStatement(): PsiChildRange { + val newLast = last!!.siblings(forward = false, withItself = false).first { it !is PsiWhiteSpace } + return PsiChildRange(first, newLast) +} diff --git a/idea/testData/intentions/convertToExpressionBody/keepComments/1.kt.after b/idea/testData/intentions/convertToExpressionBody/keepComments/1.kt.after index 39a4b2901af..1ab84ac75db 100644 --- a/idea/testData/intentions/convertToExpressionBody/keepComments/1.kt.after +++ b/idea/testData/intentions/convertToExpressionBody/keepComments/1.kt.after @@ -1 +1 @@ -fun getText(): String = "xxx" //TODO \ No newline at end of file +fun getText(): String = "xxx" //TODO diff --git a/idea/testData/intentions/convertToExpressionBody/keepComments/2.kt.after b/idea/testData/intentions/convertToExpressionBody/keepComments/2.kt.after index 54e6c1ad0bf..87ff51102a2 100644 --- a/idea/testData/intentions/convertToExpressionBody/keepComments/2.kt.after +++ b/idea/testData/intentions/convertToExpressionBody/keepComments/2.kt.after @@ -1,2 +1,2 @@ fun getText(): String = // let's return xxx - "xxx" //TODO \ No newline at end of file + "xxx" //TODO diff --git a/idea/testData/intentions/invertIfCondition/notIn.kt b/idea/testData/intentions/invertIfCondition/notIn.kt new file mode 100644 index 00000000000..9ca2606a0f6 --- /dev/null +++ b/idea/testData/intentions/invertIfCondition/notIn.kt @@ -0,0 +1,5 @@ +fun foo(p: Int) { + if (p !in 1..10) { + + } +} \ No newline at end of file diff --git a/idea/testData/intentions/invertIfCondition/notIn.kt.after b/idea/testData/intentions/invertIfCondition/notIn.kt.after new file mode 100644 index 00000000000..f22de52c951 --- /dev/null +++ b/idea/testData/intentions/invertIfCondition/notIn.kt.after @@ -0,0 +1,3 @@ +fun foo(p: Int) { + if (p in 1..10) return +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/.intention b/idea/testData/intentions/loopToCallChain/.intention new file mode 100644 index 00000000000..4f0d984b5fb --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/.intention @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.intentions.loopToCallChain.LoopToCallChainIntention \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/any_ifAssign.kt b/idea/testData/intentions/loopToCallChain/any_ifAssign.kt new file mode 100644 index 00000000000..b868dd48e20 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/any_ifAssign.kt @@ -0,0 +1,10 @@ +// WITH_RUNTIME +fun foo(list: List) { + var found = false + for (s in list) { + if (s.length > 0) { + found = true + break + } + } +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/any_ifAssign.kt.after b/idea/testData/intentions/loopToCallChain/any_ifAssign.kt.after new file mode 100644 index 00000000000..fd09759adb2 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/any_ifAssign.kt.after @@ -0,0 +1,4 @@ +// WITH_RUNTIME +fun foo(list: List) { + val found = list.any { it.length > 0 } +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/any_ifReturn.kt b/idea/testData/intentions/loopToCallChain/any_ifReturn.kt new file mode 100644 index 00000000000..4bc14f4a29e --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/any_ifReturn.kt @@ -0,0 +1,9 @@ +// WITH_RUNTIME +fun foo(list: List): Boolean { + for (s in list) { + if (s.length > 0) { + return true + } + } + return false +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/any_ifReturn.kt.after b/idea/testData/intentions/loopToCallChain/any_ifReturn.kt.after new file mode 100644 index 00000000000..9ea88dff52e --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/any_ifReturn.kt.after @@ -0,0 +1,4 @@ +// WITH_RUNTIME +fun foo(list: List): Boolean { + return list.any { it.length > 0 } +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/any_return.kt b/idea/testData/intentions/loopToCallChain/any_return.kt new file mode 100644 index 00000000000..70ee9498b99 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/any_return.kt @@ -0,0 +1,7 @@ +// WITH_RUNTIME +fun foo(list: List): Boolean { + for (s in list) { + return true + } + return false +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/any_return.kt.after b/idea/testData/intentions/loopToCallChain/any_return.kt.after new file mode 100644 index 00000000000..9fbc88d15c3 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/any_return.kt.after @@ -0,0 +1,4 @@ +// WITH_RUNTIME +fun foo(list: List): Boolean { + return list.any() +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/filterIsInstance.kt b/idea/testData/intentions/loopToCallChain/filterIsInstance.kt new file mode 100644 index 00000000000..50c2f1a3972 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/filterIsInstance.kt @@ -0,0 +1,10 @@ +// WITH_RUNTIME +fun foo(list: List): Int? { + for (o in list) { + if (o is String) { + val length = o.length + return length + } + } + return null +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/filterIsInstance.kt.after b/idea/testData/intentions/loopToCallChain/filterIsInstance.kt.after new file mode 100644 index 00000000000..520d2755b99 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/filterIsInstance.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME +fun foo(list: List): Int? { + return list + .filterIsInstance() + .map { it.length } + .firstOrNull() +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/filterIsInstance_ifContinue.kt b/idea/testData/intentions/loopToCallChain/filterIsInstance_ifContinue.kt new file mode 100644 index 00000000000..7a361f37264 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/filterIsInstance_ifContinue.kt @@ -0,0 +1,9 @@ +// WITH_RUNTIME +fun foo(list: List): Int? { + for (o in list) { + if (o !is String) continue + val length = o.length + return length + } + return null +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/filterIsInstance_ifContinue.kt.after b/idea/testData/intentions/loopToCallChain/filterIsInstance_ifContinue.kt.after new file mode 100644 index 00000000000..520d2755b99 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/filterIsInstance_ifContinue.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME +fun foo(list: List): Int? { + return list + .filterIsInstance() + .map { it.length } + .firstOrNull() +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/filterNotNull.kt b/idea/testData/intentions/loopToCallChain/filterNotNull.kt new file mode 100644 index 00000000000..9854f3b53ef --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/filterNotNull.kt @@ -0,0 +1,10 @@ +// WITH_RUNTIME +fun foo(list: List): Int? { + for (s in list) { + if (s != null) { + val length = s.length + return length + } + } + return null +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/filterNotNull.kt.after b/idea/testData/intentions/loopToCallChain/filterNotNull.kt.after new file mode 100644 index 00000000000..e058daf6cd1 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/filterNotNull.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME +fun foo(list: List): Int? { + return list + .filterNotNull() + .map { it.length } + .firstOrNull() +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/filterNotNull_ifContinue.kt b/idea/testData/intentions/loopToCallChain/filterNotNull_ifContinue.kt new file mode 100644 index 00000000000..4cad52eab77 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/filterNotNull_ifContinue.kt @@ -0,0 +1,9 @@ +// WITH_RUNTIME +fun foo(list: List): Int? { + for (o in list) { + if (o == null) continue + val code = o.hashCode() + return code + } + return null +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/filterNotNull_ifContinue.kt.after b/idea/testData/intentions/loopToCallChain/filterNotNull_ifContinue.kt.after new file mode 100644 index 00000000000..bd2c62248b0 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/filterNotNull_ifContinue.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME +fun foo(list: List): Int? { + return list + .filterNotNull() + .map { it.hashCode() } + .firstOrNull() +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/filterNot_ifContinue.kt b/idea/testData/intentions/loopToCallChain/filterNot_ifContinue.kt new file mode 100644 index 00000000000..4c5d9b99104 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/filterNot_ifContinue.kt @@ -0,0 +1,9 @@ +// WITH_RUNTIME +fun foo(list: List): Int? { + for (s in list) { + if (s.isEmpty()) continue + val l = s.length + return l + } + return null +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/filterNot_ifContinue.kt.after b/idea/testData/intentions/loopToCallChain/filterNot_ifContinue.kt.after new file mode 100644 index 00000000000..83a3cdd96ff --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/filterNot_ifContinue.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME +fun foo(list: List): Int? { + return list + .filterNot { it.isEmpty() } + .map { it.length } + .firstOrNull() +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/filter_ifContinue.kt b/idea/testData/intentions/loopToCallChain/filter_ifContinue.kt new file mode 100644 index 00000000000..f7ee7de0713 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/filter_ifContinue.kt @@ -0,0 +1,8 @@ +// WITH_RUNTIME +fun foo(list: List): String? { + for (s in list) { + if (s.isEmpty()) continue + return s + } + return null +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/filter_ifContinue.kt.after b/idea/testData/intentions/loopToCallChain/filter_ifContinue.kt.after new file mode 100644 index 00000000000..f0cb4a66221 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/filter_ifContinue.kt.after @@ -0,0 +1,4 @@ +// WITH_RUNTIME +fun foo(list: List): String? { + return list.firstOrNull { !it.isEmpty() } +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/filter_ifContinue2.kt b/idea/testData/intentions/loopToCallChain/filter_ifContinue2.kt new file mode 100644 index 00000000000..abe2d4462e8 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/filter_ifContinue2.kt @@ -0,0 +1,8 @@ +// WITH_RUNTIME +fun foo(list: List): String? { + for (s in list) { + if (!s.isEmpty()) continue + return s + } + return null +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/filter_ifContinue2.kt.after b/idea/testData/intentions/loopToCallChain/filter_ifContinue2.kt.after new file mode 100644 index 00000000000..68cbd908b51 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/filter_ifContinue2.kt.after @@ -0,0 +1,4 @@ +// WITH_RUNTIME +fun foo(list: List): String? { + return list.firstOrNull { it.isEmpty() } +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/filter_ifContinueInBlock.kt b/idea/testData/intentions/loopToCallChain/filter_ifContinueInBlock.kt new file mode 100644 index 00000000000..9c7df778b7f --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/filter_ifContinueInBlock.kt @@ -0,0 +1,10 @@ +// WITH_RUNTIME +fun foo(list: List): String? { + for (s in list) { + if (s.isEmpty()) { + continue + } + return s + } + return null +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/filter_ifContinueInBlock.kt.after b/idea/testData/intentions/loopToCallChain/filter_ifContinueInBlock.kt.after new file mode 100644 index 00000000000..f0cb4a66221 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/filter_ifContinueInBlock.kt.after @@ -0,0 +1,4 @@ +// WITH_RUNTIME +fun foo(list: List): String? { + return list.firstOrNull { !it.isEmpty() } +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/filter_ifContinueWithLabel.kt b/idea/testData/intentions/loopToCallChain/filter_ifContinueWithLabel.kt new file mode 100644 index 00000000000..b81b4c6ff44 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/filter_ifContinueWithLabel.kt @@ -0,0 +1,16 @@ +// WITH_RUNTIME +// IS_APPLICABLE: false +fun foo(): String? { + Loop@ + while (x()) { + for (s in list()) { + if (s.isEmpty()) continue@Loop + return s + } + return null + } + return null +} + +fun x(): Boolean = false +fun list(): List = listOf() \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/firstOrNull_ifAssign.kt b/idea/testData/intentions/loopToCallChain/firstOrNull_ifAssign.kt new file mode 100644 index 00000000000..913ba2f7717 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/firstOrNull_ifAssign.kt @@ -0,0 +1,10 @@ +// WITH_RUNTIME +fun foo(list: List) { + var result: String? = null + for (s in list) { + if (s.length > 0) { + result = s + break + } + } +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/firstOrNull_ifAssign.kt.after b/idea/testData/intentions/loopToCallChain/firstOrNull_ifAssign.kt.after new file mode 100644 index 00000000000..59861dac227 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/firstOrNull_ifAssign.kt.after @@ -0,0 +1,4 @@ +// WITH_RUNTIME +fun foo(list: List) { + val result: String? = list.firstOrNull { it.length > 0 } +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/firstOrNull_ifAssign_breakWithLabel.kt b/idea/testData/intentions/loopToCallChain/firstOrNull_ifAssign_breakWithLabel.kt new file mode 100644 index 00000000000..da5f9e0d895 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/firstOrNull_ifAssign_breakWithLabel.kt @@ -0,0 +1,16 @@ +// WITH_RUNTIME +// IS_APPLICABLE: false +fun foo() { + MainLoop@ + for (i in 1..10) { + var result: String? = null + for (s in list()) { + if (s.length > 0) { + result = s + break@MainLoop + } + } + } +} + +fun list(): List = listOf() \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/firstOrNull_ifAssign_cannotBeVal.kt b/idea/testData/intentions/loopToCallChain/firstOrNull_ifAssign_cannotBeVal.kt new file mode 100644 index 00000000000..68f50b6227c --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/firstOrNull_ifAssign_cannotBeVal.kt @@ -0,0 +1,12 @@ +// WITH_RUNTIME +fun foo(list: List) { + var result: String? = null + for (s in list) { + if (s.length > 0) { + result = s + break + } + } + + result += "1" +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/firstOrNull_ifAssign_cannotBeVal.kt.after b/idea/testData/intentions/loopToCallChain/firstOrNull_ifAssign_cannotBeVal.kt.after new file mode 100644 index 00000000000..62b74844596 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/firstOrNull_ifAssign_cannotBeVal.kt.after @@ -0,0 +1,6 @@ +// WITH_RUNTIME +fun foo(list: List) { + var result: String? = list.firstOrNull { it.length > 0 } + + result += "1" +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/firstOrNull_ifAssign_preserveComments.kt b/idea/testData/intentions/loopToCallChain/firstOrNull_ifAssign_preserveComments.kt new file mode 100644 index 00000000000..ee520cce9b1 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/firstOrNull_ifAssign_preserveComments.kt @@ -0,0 +1,10 @@ +// WITH_RUNTIME +fun foo(list: List) { + var result: String? = null + for (s in list) { // search for first non-empty string in the list + if (s.length > 0) { // string should be non-empty + result = s // save it into result + break + } + } +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/firstOrNull_ifAssign_preserveComments.kt.after b/idea/testData/intentions/loopToCallChain/firstOrNull_ifAssign_preserveComments.kt.after new file mode 100644 index 00000000000..a9b987bec34 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/firstOrNull_ifAssign_preserveComments.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME +fun foo(list: List) { + val result: String? = list.firstOrNull { // search for first non-empty string in the list + it.length > 0 + }// string should be non-empty + // save it into result +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/firstOrNull_ifAssign_variableUsedBefore.kt b/idea/testData/intentions/loopToCallChain/firstOrNull_ifAssign_variableUsedBefore.kt new file mode 100644 index 00000000000..16769bfd896 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/firstOrNull_ifAssign_variableUsedBefore.kt @@ -0,0 +1,11 @@ +// WITH_RUNTIME +// IS_APPLICABLE: false +fun foo(list: List) { + var result: String? = null + for (s in list) { + if (s != result) { + result = s + break + } + } +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/firstOrNull_ifReturn.kt b/idea/testData/intentions/loopToCallChain/firstOrNull_ifReturn.kt new file mode 100644 index 00000000000..dbeb013cf82 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/firstOrNull_ifReturn.kt @@ -0,0 +1,9 @@ +// WITH_RUNTIME +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/testData/intentions/loopToCallChain/firstOrNull_ifReturn.kt.after b/idea/testData/intentions/loopToCallChain/firstOrNull_ifReturn.kt.after new file mode 100644 index 00000000000..8e5db467919 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/firstOrNull_ifReturn.kt.after @@ -0,0 +1,4 @@ +// WITH_RUNTIME +fun foo(list: List): String? { + return list.firstOrNull { it.length > 0 } +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/firstOrNull_return.kt b/idea/testData/intentions/loopToCallChain/firstOrNull_return.kt new file mode 100644 index 00000000000..1ddc0d2242f --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/firstOrNull_return.kt @@ -0,0 +1,7 @@ +// WITH_RUNTIME +fun foo(list: List): String? { + for (s in list) { + return s + } + return null +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/firstOrNull_return.kt.after b/idea/testData/intentions/loopToCallChain/firstOrNull_return.kt.after new file mode 100644 index 00000000000..8b73d9828be --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/firstOrNull_return.kt.after @@ -0,0 +1,4 @@ +// WITH_RUNTIME +fun foo(list: List): String? { + return list.firstOrNull() +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/firstOrNull_returnExpression.kt b/idea/testData/intentions/loopToCallChain/firstOrNull_returnExpression.kt new file mode 100644 index 00000000000..0b5313bec07 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/firstOrNull_returnExpression.kt @@ -0,0 +1,10 @@ +// WITH_RUNTIME +// IS_APPLICABLE: false +fun foo(list: List): Int? { + for (s in list) { + if (s.isNotEmpty()) { + return s.length + } + } + return null +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/firstOrNull_returnNotNullIfNone.kt b/idea/testData/intentions/loopToCallChain/firstOrNull_returnNotNullIfNone.kt new file mode 100644 index 00000000000..a2ff283ea35 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/firstOrNull_returnNotNullIfNone.kt @@ -0,0 +1,10 @@ +// WITH_RUNTIME +// IS_APPLICABLE: false +fun foo(list: List): String { + for (s in list) { + if (s.isNotEmpty()) { + return s + } + } + return "" +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/firstOrNull_return_comment.kt b/idea/testData/intentions/loopToCallChain/firstOrNull_return_comment.kt new file mode 100644 index 00000000000..f6361b940e7 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/firstOrNull_return_comment.kt @@ -0,0 +1,8 @@ +// WITH_RUNTIME +fun foo(list: List): String? { + for (s in list) { + return s + } + // return null if not found + return null +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/firstOrNull_return_comment.kt.after b/idea/testData/intentions/loopToCallChain/firstOrNull_return_comment.kt.after new file mode 100644 index 00000000000..6c995a36a6d --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/firstOrNull_return_comment.kt.after @@ -0,0 +1,5 @@ +// WITH_RUNTIME +fun foo(list: List): String? { + // return null if not found + return list.firstOrNull() +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/flatMap.kt b/idea/testData/intentions/loopToCallChain/flatMap.kt new file mode 100644 index 00000000000..c47aa331112 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/flatMap.kt @@ -0,0 +1,9 @@ +// WITH_RUNTIME +fun foo(list: List): String? { + for (s in list) { + for (line in s.lines()) { + if (line.isNotBlank()) return line + } + } + return null +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/flatMap.kt.after b/idea/testData/intentions/loopToCallChain/flatMap.kt.after new file mode 100644 index 00000000000..5f01a657855 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/flatMap.kt.after @@ -0,0 +1,6 @@ +// WITH_RUNTIME +fun foo(list: List): String? { + return list + .flatMap { it.lines() } + .firstOrNull { it.isNotBlank() } +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/flatMapWithBreak.kt b/idea/testData/intentions/loopToCallChain/flatMapWithBreak.kt new file mode 100644 index 00000000000..2640ad0df8f --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/flatMapWithBreak.kt @@ -0,0 +1,14 @@ +// WITH_RUNTIME +fun foo(list: List): String? { + var result: String? = null + MainLoop@ + for (s in list) { + for (line in s.lines()) { + if (line.isNotBlank()) { + result = line + break@MainLoop + } + } + } + return result +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/flatMapWithBreak.kt.after b/idea/testData/intentions/loopToCallChain/flatMapWithBreak.kt.after new file mode 100644 index 00000000000..5032a175aef --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/flatMapWithBreak.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME +fun foo(list: List): String? { + val result: String? = list + .flatMap { it.lines() } + .firstOrNull { it.isNotBlank() } + return result +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/flatMapWithWrongBreak.kt b/idea/testData/intentions/loopToCallChain/flatMapWithWrongBreak.kt new file mode 100644 index 00000000000..bc287cf6c91 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/flatMapWithWrongBreak.kt @@ -0,0 +1,14 @@ +// WITH_RUNTIME +// IS_APPLICABLE: false +fun foo(list: List): String? { + var result: String? = null + for (s in list) { + for (line in s.lines()) { + if (line.isNotBlank()) { + result = line + break + } + } + } + return result +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/flatMap_notIterable.kt b/idea/testData/intentions/loopToCallChain/flatMap_notIterable.kt new file mode 100644 index 00000000000..d785cf65247 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/flatMap_notIterable.kt @@ -0,0 +1,10 @@ +// WITH_RUNTIME +// IS_APPLICABLE: false +fun foo(list: List): Char? { + for (s in list) { + for (c in s) { + if (c != ' ') return c + } + } + return null +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/itAlreadyUsed.kt b/idea/testData/intentions/loopToCallChain/itAlreadyUsed.kt new file mode 100644 index 00000000000..49a0b5f5737 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/itAlreadyUsed.kt @@ -0,0 +1,10 @@ +// WITH_RUNTIME +fun foo(list: List, it: Int) { + var found = false + for (s in list) { + if (s.length > it) { + found = true + break + } + } +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/itAlreadyUsed.kt.after b/idea/testData/intentions/loopToCallChain/itAlreadyUsed.kt.after new file mode 100644 index 00000000000..5f97566217b --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/itAlreadyUsed.kt.after @@ -0,0 +1,4 @@ +// WITH_RUNTIME +fun foo(list: List, it: Int) { + val found = list.any { s -> s.length > it } +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/map.kt b/idea/testData/intentions/loopToCallChain/map.kt new file mode 100644 index 00000000000..0d8950238d0 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/map.kt @@ -0,0 +1,8 @@ +// WITH_RUNTIME +fun foo(list: List): Int? { + for (s in list) { + val length = s.length + if (length > 0) return length + } + return null +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/map.kt.after b/idea/testData/intentions/loopToCallChain/map.kt.after new file mode 100644 index 00000000000..5311e57e96a --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/map.kt.after @@ -0,0 +1,6 @@ +// WITH_RUNTIME +fun foo(list: List): Int? { + return list + .map { it.length } + .firstOrNull { it > 0 } +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/mapVar.kt b/idea/testData/intentions/loopToCallChain/mapVar.kt new file mode 100644 index 00000000000..eb80ed93774 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/mapVar.kt @@ -0,0 +1,8 @@ +// WITH_RUNTIME +fun foo(list: List): Int? { + for (s in list) { + var length = s.length + if (length > 0) return length + } + return null +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/mapVar.kt.after b/idea/testData/intentions/loopToCallChain/mapVar.kt.after new file mode 100644 index 00000000000..5311e57e96a --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/mapVar.kt.after @@ -0,0 +1,6 @@ +// WITH_RUNTIME +fun foo(list: List): Int? { + return list + .map { it.length } + .firstOrNull { it > 0 } +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/map_var.kt b/idea/testData/intentions/loopToCallChain/map_var.kt new file mode 100644 index 00000000000..f3b31c57f11 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/map_var.kt @@ -0,0 +1,9 @@ +// WITH_RUNTIME +// IS_APPLICABLE: false +fun foo(list: List): Int? { + for (s in list) { + var length = s.length + if (length > 0) return ++length + } + return null +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/map_variableStillNeeded.kt b/idea/testData/intentions/loopToCallChain/map_variableStillNeeded.kt new file mode 100644 index 00000000000..45118e9ca89 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/map_variableStillNeeded.kt @@ -0,0 +1,9 @@ +// WITH_RUNTIME +// IS_APPLICABLE: false +fun foo(list: List): String? { + for (s in list) { + val length = s.length + if (length > 0) return s + length + } + return null +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/none_ifReturn.kt b/idea/testData/intentions/loopToCallChain/none_ifReturn.kt new file mode 100644 index 00000000000..b96faa94eb8 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/none_ifReturn.kt @@ -0,0 +1,9 @@ +// WITH_RUNTIME +fun foo(list: List): Boolean { + for (s in list) { + if (s.length > 0) { + return false + } + } + return true +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/none_ifReturn.kt.after b/idea/testData/intentions/loopToCallChain/none_ifReturn.kt.after new file mode 100644 index 00000000000..2113034825a --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/none_ifReturn.kt.after @@ -0,0 +1,4 @@ +// WITH_RUNTIME +fun foo(list: List): Boolean { + return list.none { it.length > 0 } +} \ No newline at end of file