Initial implementation of loop-to-call-chain intention
This commit is contained in:
+2133
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
+10
-6
@@ -37,18 +37,22 @@ abstract class IntentionBasedInspection<TElement : PsiElement>(
|
||||
) : AbstractKotlinInspection() {
|
||||
|
||||
constructor(
|
||||
intention: SelfTargetingRangeIntention<TElement>
|
||||
) : this(listOf(IntentionData(intention)), null, intention.elementType)
|
||||
intention: SelfTargetingRangeIntention<TElement>,
|
||||
problemText: String? = null
|
||||
) : this(listOf(IntentionData(intention)), problemText, intention.elementType)
|
||||
|
||||
constructor(
|
||||
intention: SelfTargetingRangeIntention<TElement>,
|
||||
additionalChecker: (TElement, IntentionBasedInspection<TElement>) -> Boolean
|
||||
) : this(listOf(IntentionData(intention, additionalChecker)), null, intention.elementType)
|
||||
additionalChecker: (TElement, IntentionBasedInspection<TElement>) -> Boolean,
|
||||
problemText: String? = null
|
||||
) : this(listOf(IntentionData(intention, additionalChecker)), problemText, intention.elementType)
|
||||
|
||||
constructor(
|
||||
intention: SelfTargetingRangeIntention<TElement>,
|
||||
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<TElement : PsiElement>(
|
||||
|
||||
@@ -25,12 +25,12 @@ import java.util.*
|
||||
class KtForLoopInReference(element: KtForExpression) : KtMultiReference<KtForExpression>(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<DeclarationDescriptor> {
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<html>
|
||||
<body>
|
||||
This inspection reports for-loops that can be replaced with a sequence of stdlib-operations (like "map", "filter" etc)
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,3 @@
|
||||
fun foo(list: List<String>): String? {
|
||||
<spot>return list.firstOrNull { s -> s.length > 0 }</spot>
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fun foo(list: List<String>): String? {
|
||||
<spot>for (s in list) {
|
||||
if (s.length > 0) {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return null</spot>
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<html>
|
||||
<body>
|
||||
This intention converts a for-loop into a sequence of stdlib-operations (like "map", "filter" etc)
|
||||
</body>
|
||||
</html>
|
||||
@@ -1013,6 +1013,11 @@
|
||||
<category>Kotlin</category>
|
||||
</intentionAction>
|
||||
|
||||
<intentionAction>
|
||||
<className>org.jetbrains.kotlin.idea.intentions.loopToCallChain.LoopToCallChainIntention</className>
|
||||
<category>Kotlin</category>
|
||||
</intentionAction>
|
||||
|
||||
<intentionAction>
|
||||
<className>org.jetbrains.kotlin.idea.intentions.SwapBinaryExpressionIntention</className>
|
||||
<category>Kotlin</category>
|
||||
@@ -1507,6 +1512,13 @@
|
||||
language="kotlin"
|
||||
/>
|
||||
|
||||
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.loopToCallChain.LoopToCallChainInspection"
|
||||
displayName="Loop can be replaced with stdlib operations"
|
||||
groupName="Kotlin"
|
||||
enabledByDefault="true"
|
||||
level="INFO"
|
||||
/>
|
||||
|
||||
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.ConflictingExtensionPropertyInspection"
|
||||
displayName="Extension property conflicting with synthetic one"
|
||||
groupName="Kotlin"
|
||||
|
||||
@@ -32,9 +32,8 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
|
||||
class RemoveForLoopIndicesInspection : IntentionBasedInspection<KtForExpression>(
|
||||
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
|
||||
|
||||
+44
@@ -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<KtForExpression>(
|
||||
LoopToCallChainIntention(),
|
||||
problemText = "Loop can be replaced with stdlib operations")
|
||||
|
||||
class LoopToCallChainIntention : SelfTargetingRangeIntention<KtForExpression>(
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -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<KtExpression>,
|
||||
val workingVariable: KtCallableDeclaration,
|
||||
val indexVariable: KtCallableDeclaration?,
|
||||
val loop: KtForExpression
|
||||
)
|
||||
|
||||
interface SequenceTransformationMatcher {
|
||||
fun match(state: MatchingState): SequenceTransformationMatch?
|
||||
}
|
||||
|
||||
class SequenceTransformationMatch(
|
||||
val transformations: Collection<SequenceTransformation>,
|
||||
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<SequenceTransformation> = listOf()
|
||||
)
|
||||
@@ -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<SequenceTransformationMatcher> = listOf(
|
||||
FilterTransformation.Matcher,
|
||||
MapTransformation.Matcher,
|
||||
FlatMapTransformation.Matcher
|
||||
)
|
||||
|
||||
val resultMatchers: Collection<ResultTransformationMatcher> = listOf(
|
||||
FindAndReturnTransformation.Matcher,
|
||||
FindAndAssignTransformation.Matcher
|
||||
)
|
||||
}
|
||||
|
||||
fun match(loop: KtForExpression): ResultTransformationMatch? {
|
||||
val sequenceTransformations = ArrayList<SequenceTransformation>()
|
||||
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
|
||||
}
|
||||
|
||||
+92
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+77
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+123
@@ -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()")
|
||||
}
|
||||
}
|
||||
+68
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+56
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<KtNameReferenceExpression> {
|
||||
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<KtNameReferenceExpression> {
|
||||
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<KtElement>): 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<KtLoopExpression>()
|
||||
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<KtExpression>()
|
||||
}
|
||||
|
||||
fun KtExpression.nextStatement(): KtExpression? {
|
||||
var statement = unwrapIfLabeled()
|
||||
if (statement.parent !is KtBlockExpression) return null
|
||||
return statement.siblings(forward = true, withItself = false).firstIsInstanceOrNull<KtExpression>()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
fun getText(): String = "xxx" //TODO
|
||||
fun getText(): String = "xxx" //TODO
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
fun getText(): String = // let's return xxx
|
||||
"xxx" //TODO
|
||||
"xxx" //TODO
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
fun foo(p: Int) {
|
||||
<caret>if (p !in 1..10) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fun foo(p: Int) {
|
||||
if (p in 1..10) return
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
org.jetbrains.kotlin.idea.intentions.loopToCallChain.LoopToCallChainIntention
|
||||
@@ -0,0 +1,10 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String>) {
|
||||
var found = false
|
||||
<caret>for (s in list) {
|
||||
if (s.length > 0) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String>) {
|
||||
<caret>val found = list.any { it.length > 0 }
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String>): Boolean {
|
||||
<caret>for (s in list) {
|
||||
if (s.length > 0) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String>): Boolean {
|
||||
<caret>return list.any { it.length > 0 }
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String>): Boolean {
|
||||
<caret>for (s in list) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String>): Boolean {
|
||||
<caret>return list.any()
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<Any>): Int? {
|
||||
<caret>for (o in list) {
|
||||
if (o is String) {
|
||||
val length = o.length
|
||||
return length
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<Any>): Int? {
|
||||
return list
|
||||
.filterIsInstance<String>()
|
||||
.map { it.length }
|
||||
.firstOrNull()
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<Any>): Int? {
|
||||
<caret>for (o in list) {
|
||||
if (o !is String) continue
|
||||
val length = o.length
|
||||
return length
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<Any>): Int? {
|
||||
return list
|
||||
.filterIsInstance<String>()
|
||||
.map { it.length }
|
||||
.firstOrNull()
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String?>): Int? {
|
||||
<caret>for (s in list) {
|
||||
if (s != null) {
|
||||
val length = s.length
|
||||
return length
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String?>): Int? {
|
||||
<caret>return list
|
||||
.filterNotNull()
|
||||
.map { it.length }
|
||||
.firstOrNull()
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<Any?>): Int? {
|
||||
<caret>for (o in list) {
|
||||
if (o == null) continue
|
||||
val code = o.hashCode()
|
||||
return code
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<Any?>): Int? {
|
||||
<caret>return list
|
||||
.filterNotNull()
|
||||
.map { it.hashCode() }
|
||||
.firstOrNull()
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String>): Int? {
|
||||
<caret>for (s in list) {
|
||||
if (s.isEmpty()) continue
|
||||
val l = s.length
|
||||
return l
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String>): Int? {
|
||||
<caret>return list
|
||||
.filterNot { it.isEmpty() }
|
||||
.map { it.length }
|
||||
.firstOrNull()
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String>): String? {
|
||||
<caret>for (s in list) {
|
||||
if (s.isEmpty()) continue
|
||||
return s
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String>): String? {
|
||||
<caret>return list.firstOrNull { !it.isEmpty() }
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String>): String? {
|
||||
<caret>for (s in list) {
|
||||
if (!s.isEmpty()) continue
|
||||
return s
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String>): String? {
|
||||
<caret>return list.firstOrNull { it.isEmpty() }
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String>): String? {
|
||||
<caret>for (s in list) {
|
||||
if (s.isEmpty()) {
|
||||
continue
|
||||
}
|
||||
return s
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String>): String? {
|
||||
<caret>return list.firstOrNull { !it.isEmpty() }
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// WITH_RUNTIME
|
||||
// IS_APPLICABLE: false
|
||||
fun foo(): String? {
|
||||
Loop@
|
||||
while (x()) {
|
||||
<caret>for (s in list()) {
|
||||
if (s.isEmpty()) continue@Loop
|
||||
return s
|
||||
}
|
||||
return null
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun x(): Boolean = false
|
||||
fun list(): List<String> = listOf()
|
||||
@@ -0,0 +1,10 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String>) {
|
||||
var result: String? = null
|
||||
<caret>for (s in list) {
|
||||
if (s.length > 0) {
|
||||
result = s
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String>) {
|
||||
<caret>val result: String? = list.firstOrNull { it.length > 0 }
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
// WITH_RUNTIME
|
||||
// IS_APPLICABLE: false
|
||||
fun foo() {
|
||||
MainLoop@
|
||||
for (i in 1..10) {
|
||||
var result: String? = null
|
||||
<caret>for (s in list()) {
|
||||
if (s.length > 0) {
|
||||
result = s
|
||||
break@MainLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun list(): List<String> = listOf()
|
||||
@@ -0,0 +1,12 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String>) {
|
||||
var result: String? = null
|
||||
<caret>for (s in list) {
|
||||
if (s.length > 0) {
|
||||
result = s
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
result += "1"
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String>) {
|
||||
<caret>var result: String? = list.firstOrNull { it.length > 0 }
|
||||
|
||||
result += "1"
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String>) {
|
||||
var result: String? = null
|
||||
<caret>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
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String>) {
|
||||
<caret>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
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// WITH_RUNTIME
|
||||
// IS_APPLICABLE: false
|
||||
fun foo(list: List<String>) {
|
||||
var result: String? = null
|
||||
<caret>for (s in list) {
|
||||
if (s != result) {
|
||||
result = s
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String>): String? {
|
||||
<caret>for (s in list) {
|
||||
if (s.length > 0) {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String>): String? {
|
||||
<caret>return list.firstOrNull { it.length > 0 }
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String>): String? {
|
||||
<caret>for (s in list) {
|
||||
return s
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String>): String? {
|
||||
<caret>return list.firstOrNull()
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// WITH_RUNTIME
|
||||
// IS_APPLICABLE: false
|
||||
fun foo(list: List<String>): Int? {
|
||||
<caret>for (s in list) {
|
||||
if (s.isNotEmpty()) {
|
||||
return s.length
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// WITH_RUNTIME
|
||||
// IS_APPLICABLE: false
|
||||
fun foo(list: List<String>): String {
|
||||
<caret>for (s in list) {
|
||||
if (s.isNotEmpty()) {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String>): String? {
|
||||
<caret>for (s in list) {
|
||||
return s
|
||||
}
|
||||
// return null if not found
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String>): String? {
|
||||
// return null if not found
|
||||
return list.firstOrNull()
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String>): String? {
|
||||
<caret>for (s in list) {
|
||||
for (line in s.lines()) {
|
||||
if (line.isNotBlank()) return line
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String>): String? {
|
||||
<caret>return list
|
||||
.flatMap { it.lines() }
|
||||
.firstOrNull { it.isNotBlank() }
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String>): String? {
|
||||
var result: String? = null
|
||||
MainLoop@
|
||||
<caret>for (s in list) {
|
||||
for (line in s.lines()) {
|
||||
if (line.isNotBlank()) {
|
||||
result = line
|
||||
break@MainLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String>): String? {
|
||||
<caret>val result: String? = list
|
||||
.flatMap { it.lines() }
|
||||
.firstOrNull { it.isNotBlank() }
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// WITH_RUNTIME
|
||||
// IS_APPLICABLE: false
|
||||
fun foo(list: List<String>): String? {
|
||||
var result: String? = null
|
||||
<caret>for (s in list) {
|
||||
for (line in s.lines()) {
|
||||
if (line.isNotBlank()) {
|
||||
result = line
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// WITH_RUNTIME
|
||||
// IS_APPLICABLE: false
|
||||
fun foo(list: List<String>): Char? {
|
||||
<caret>for (s in list) {
|
||||
for (c in s) {
|
||||
if (c != ' ') return c
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String>, it: Int) {
|
||||
var found = false
|
||||
<caret>for (s in list) {
|
||||
if (s.length > it) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String>, it: Int) {
|
||||
<caret>val found = list.any { s -> s.length > it }
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String>): Int? {
|
||||
<caret>for (s in list) {
|
||||
val length = s.length
|
||||
if (length > 0) return length
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String>): Int? {
|
||||
<caret>return list
|
||||
.map { it.length }
|
||||
.firstOrNull { it > 0 }
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String>): Int? {
|
||||
<caret>for (s in list) {
|
||||
var length = s.length
|
||||
if (length > 0) return length
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String>): Int? {
|
||||
<caret>return list
|
||||
.map { it.length }
|
||||
.firstOrNull { it > 0 }
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// WITH_RUNTIME
|
||||
// IS_APPLICABLE: false
|
||||
fun foo(list: List<String>): Int? {
|
||||
<caret>for (s in list) {
|
||||
var length = s.length
|
||||
if (length > 0) return ++length
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// WITH_RUNTIME
|
||||
// IS_APPLICABLE: false
|
||||
fun foo(list: List<String>): String? {
|
||||
<caret>for (s in list) {
|
||||
val length = s.length
|
||||
if (length > 0) return s + length
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String>): Boolean {
|
||||
<caret>for (s in list) {
|
||||
if (s.length > 0) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(list: List<String>): Boolean {
|
||||
<caret>return list.none { it.length > 0 }
|
||||
}
|
||||
Reference in New Issue
Block a user