From ba0efda2f0d59f4fe259db5512cbd478d4d25dd9 Mon Sep 17 00:00:00 2001 From: Anton Sukhonosenko Date: Sat, 2 Jan 2016 16:52:05 +0300 Subject: [PATCH 1/2] Suggest to replace 'substring' calls by take/drop/dropLast calls when possible #KT-10196 Fixed --- .../after.kt.template | 1 + .../before.kt.template | 1 + .../description.html | 5 + .../after.kt.template | 1 + .../before.kt.template | 1 + .../description.html | 5 + .../after.kt.template | 1 + .../before.kt.template | 1 + .../description.html | 5 + .../after.kt.template | 1 + .../before.kt.template | 1 + .../description.html | 5 + .../after.kt.template | 1 + .../before.kt.template | 1 + .../description.html | 5 + idea/src/META-INF/plugin.xml | 25 +++ .../intentions/ReplaceSubstringIntention.kt | 52 ++++++ .../ReplaceSubstringWithDropIntention.kt | 39 +++++ .../ReplaceSubstringWithDropLastIntention.kt | 55 ++++++ ...tringWithSubstringBeforeAfterIntentions.kt | 81 +++++++++ .../ReplaceSubstringWithTakeIntention.kt | 41 +++++ .../replaceSubstringWithDrop/.intention | 1 + .../replaceWithDrop.kt | 5 + .../replaceWithDrop.kt.after | 5 + .../replaceSubstringWithDrop/semicolon.kt | 5 + .../semicolon.kt.after | 5 + .../substringWithTwoArguments.kt | 6 + .../replaceSubstringWithDropLast/.intention | 1 + .../immutableProperty.kt | 7 + .../immutableProperty.kt.after | 7 + .../nonZeroFirstArgument.kt | 6 + .../replaceWithDropLast.kt | 5 + .../replaceWithDropLast.kt.after | 5 + .../replaceSubstringWithDropLast/semicolon.kt | 5 + .../semicolon.kt.after | 5 + .../.intention | 1 + .../immutableProperty.kt | 7 + .../immutableProperty.kt.after | 7 + .../replaceWithSubstringAfter.kt | 5 + .../replaceWithSubstringAfter.kt.after | 5 + .../semicolon.kt | 5 + .../semicolon.kt.after | 5 + .../.intention | 1 + .../immutableProperty.kt | 7 + .../immutableProperty.kt.after | 7 + .../nonZeroFirstArgument.kt | 6 + .../replaceWithSubstringBefore.kt | 5 + .../replaceWithSubstringBefore.kt.after | 5 + .../semicolon.kt | 5 + .../semicolon.kt.after | 5 + .../replaceSubstringWithTake/.intention | 1 + .../constantAsFirstArgument.kt | 8 + .../expressionAsFirstArgument.kt | 6 + .../nonZeroFirstArgument.kt | 6 + .../replaceWithTake.kt | 5 + .../replaceWithTake.kt.after | 5 + .../replaceSubstringWithTake/semicolon.kt | 5 + .../semicolon.kt.after | 5 + .../intentions/IntentionTestGenerated.java | 159 ++++++++++++++++++ 59 files changed, 672 insertions(+) create mode 100644 idea/resources/intentionDescriptions/ReplaceSubstringWithDropIntention/after.kt.template create mode 100644 idea/resources/intentionDescriptions/ReplaceSubstringWithDropIntention/before.kt.template create mode 100644 idea/resources/intentionDescriptions/ReplaceSubstringWithDropIntention/description.html create mode 100644 idea/resources/intentionDescriptions/ReplaceSubstringWithDropLastIntention/after.kt.template create mode 100644 idea/resources/intentionDescriptions/ReplaceSubstringWithDropLastIntention/before.kt.template create mode 100644 idea/resources/intentionDescriptions/ReplaceSubstringWithDropLastIntention/description.html create mode 100644 idea/resources/intentionDescriptions/ReplaceSubstringWithSubstringAfterIntention/after.kt.template create mode 100644 idea/resources/intentionDescriptions/ReplaceSubstringWithSubstringAfterIntention/before.kt.template create mode 100644 idea/resources/intentionDescriptions/ReplaceSubstringWithSubstringAfterIntention/description.html create mode 100644 idea/resources/intentionDescriptions/ReplaceSubstringWithSubstringBeforeIntention/after.kt.template create mode 100644 idea/resources/intentionDescriptions/ReplaceSubstringWithSubstringBeforeIntention/before.kt.template create mode 100644 idea/resources/intentionDescriptions/ReplaceSubstringWithSubstringBeforeIntention/description.html create mode 100644 idea/resources/intentionDescriptions/ReplaceSubstringWithTakeIntention/after.kt.template create mode 100644 idea/resources/intentionDescriptions/ReplaceSubstringWithTakeIntention/before.kt.template create mode 100644 idea/resources/intentionDescriptions/ReplaceSubstringWithTakeIntention/description.html create mode 100644 idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSubstringIntention.kt create mode 100644 idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSubstringWithDropIntention.kt create mode 100644 idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSubstringWithDropLastIntention.kt create mode 100644 idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSubstringWithSubstringBeforeAfterIntentions.kt create mode 100644 idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSubstringWithTakeIntention.kt create mode 100644 idea/testData/intentions/replaceSubstringWithDrop/.intention create mode 100644 idea/testData/intentions/replaceSubstringWithDrop/replaceWithDrop.kt create mode 100644 idea/testData/intentions/replaceSubstringWithDrop/replaceWithDrop.kt.after create mode 100644 idea/testData/intentions/replaceSubstringWithDrop/semicolon.kt create mode 100644 idea/testData/intentions/replaceSubstringWithDrop/semicolon.kt.after create mode 100644 idea/testData/intentions/replaceSubstringWithDrop/substringWithTwoArguments.kt create mode 100644 idea/testData/intentions/replaceSubstringWithDropLast/.intention create mode 100644 idea/testData/intentions/replaceSubstringWithDropLast/immutableProperty.kt create mode 100644 idea/testData/intentions/replaceSubstringWithDropLast/immutableProperty.kt.after create mode 100644 idea/testData/intentions/replaceSubstringWithDropLast/nonZeroFirstArgument.kt create mode 100644 idea/testData/intentions/replaceSubstringWithDropLast/replaceWithDropLast.kt create mode 100644 idea/testData/intentions/replaceSubstringWithDropLast/replaceWithDropLast.kt.after create mode 100644 idea/testData/intentions/replaceSubstringWithDropLast/semicolon.kt create mode 100644 idea/testData/intentions/replaceSubstringWithDropLast/semicolon.kt.after create mode 100644 idea/testData/intentions/replaceSubstringWithSubstringAfter/.intention create mode 100644 idea/testData/intentions/replaceSubstringWithSubstringAfter/immutableProperty.kt create mode 100644 idea/testData/intentions/replaceSubstringWithSubstringAfter/immutableProperty.kt.after create mode 100644 idea/testData/intentions/replaceSubstringWithSubstringAfter/replaceWithSubstringAfter.kt create mode 100644 idea/testData/intentions/replaceSubstringWithSubstringAfter/replaceWithSubstringAfter.kt.after create mode 100644 idea/testData/intentions/replaceSubstringWithSubstringAfter/semicolon.kt create mode 100644 idea/testData/intentions/replaceSubstringWithSubstringAfter/semicolon.kt.after create mode 100644 idea/testData/intentions/replaceSubstringWithSubstringBefore/.intention create mode 100644 idea/testData/intentions/replaceSubstringWithSubstringBefore/immutableProperty.kt create mode 100644 idea/testData/intentions/replaceSubstringWithSubstringBefore/immutableProperty.kt.after create mode 100644 idea/testData/intentions/replaceSubstringWithSubstringBefore/nonZeroFirstArgument.kt create mode 100644 idea/testData/intentions/replaceSubstringWithSubstringBefore/replaceWithSubstringBefore.kt create mode 100644 idea/testData/intentions/replaceSubstringWithSubstringBefore/replaceWithSubstringBefore.kt.after create mode 100644 idea/testData/intentions/replaceSubstringWithSubstringBefore/semicolon.kt create mode 100644 idea/testData/intentions/replaceSubstringWithSubstringBefore/semicolon.kt.after create mode 100644 idea/testData/intentions/replaceSubstringWithTake/.intention create mode 100644 idea/testData/intentions/replaceSubstringWithTake/constantAsFirstArgument.kt create mode 100644 idea/testData/intentions/replaceSubstringWithTake/expressionAsFirstArgument.kt create mode 100644 idea/testData/intentions/replaceSubstringWithTake/nonZeroFirstArgument.kt create mode 100644 idea/testData/intentions/replaceSubstringWithTake/replaceWithTake.kt create mode 100644 idea/testData/intentions/replaceSubstringWithTake/replaceWithTake.kt.after create mode 100644 idea/testData/intentions/replaceSubstringWithTake/semicolon.kt create mode 100644 idea/testData/intentions/replaceSubstringWithTake/semicolon.kt.after diff --git a/idea/resources/intentionDescriptions/ReplaceSubstringWithDropIntention/after.kt.template b/idea/resources/intentionDescriptions/ReplaceSubstringWithDropIntention/after.kt.template new file mode 100644 index 00000000000..bc7391ca86c --- /dev/null +++ b/idea/resources/intentionDescriptions/ReplaceSubstringWithDropIntention/after.kt.template @@ -0,0 +1 @@ +s.drop(5) \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/ReplaceSubstringWithDropIntention/before.kt.template b/idea/resources/intentionDescriptions/ReplaceSubstringWithDropIntention/before.kt.template new file mode 100644 index 00000000000..6ede901326c --- /dev/null +++ b/idea/resources/intentionDescriptions/ReplaceSubstringWithDropIntention/before.kt.template @@ -0,0 +1 @@ +s.substring(5) \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/ReplaceSubstringWithDropIntention/description.html b/idea/resources/intentionDescriptions/ReplaceSubstringWithDropIntention/description.html new file mode 100644 index 00000000000..df9b27cfaa7 --- /dev/null +++ b/idea/resources/intentionDescriptions/ReplaceSubstringWithDropIntention/description.html @@ -0,0 +1,5 @@ + + +This intention replaces 'substring' call with 'drop' call. + + diff --git a/idea/resources/intentionDescriptions/ReplaceSubstringWithDropLastIntention/after.kt.template b/idea/resources/intentionDescriptions/ReplaceSubstringWithDropLastIntention/after.kt.template new file mode 100644 index 00000000000..b6c185a07d9 --- /dev/null +++ b/idea/resources/intentionDescriptions/ReplaceSubstringWithDropLastIntention/after.kt.template @@ -0,0 +1 @@ +s.dropLast(5) \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/ReplaceSubstringWithDropLastIntention/before.kt.template b/idea/resources/intentionDescriptions/ReplaceSubstringWithDropLastIntention/before.kt.template new file mode 100644 index 00000000000..c9da37c9725 --- /dev/null +++ b/idea/resources/intentionDescriptions/ReplaceSubstringWithDropLastIntention/before.kt.template @@ -0,0 +1 @@ +s.substring(0, s.length - 5) \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/ReplaceSubstringWithDropLastIntention/description.html b/idea/resources/intentionDescriptions/ReplaceSubstringWithDropLastIntention/description.html new file mode 100644 index 00000000000..40ed08cd378 --- /dev/null +++ b/idea/resources/intentionDescriptions/ReplaceSubstringWithDropLastIntention/description.html @@ -0,0 +1,5 @@ + + +This intention replaces 'substring' call with 'dropLast' call. + + diff --git a/idea/resources/intentionDescriptions/ReplaceSubstringWithSubstringAfterIntention/after.kt.template b/idea/resources/intentionDescriptions/ReplaceSubstringWithSubstringAfterIntention/after.kt.template new file mode 100644 index 00000000000..969bc06ec1b --- /dev/null +++ b/idea/resources/intentionDescriptions/ReplaceSubstringWithSubstringAfterIntention/after.kt.template @@ -0,0 +1 @@ +s.substringAfter('x') \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/ReplaceSubstringWithSubstringAfterIntention/before.kt.template b/idea/resources/intentionDescriptions/ReplaceSubstringWithSubstringAfterIntention/before.kt.template new file mode 100644 index 00000000000..a4812c12b19 --- /dev/null +++ b/idea/resources/intentionDescriptions/ReplaceSubstringWithSubstringAfterIntention/before.kt.template @@ -0,0 +1 @@ +s.substring(s.indexOf('x')) \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/ReplaceSubstringWithSubstringAfterIntention/description.html b/idea/resources/intentionDescriptions/ReplaceSubstringWithSubstringAfterIntention/description.html new file mode 100644 index 00000000000..883344cd71d --- /dev/null +++ b/idea/resources/intentionDescriptions/ReplaceSubstringWithSubstringAfterIntention/description.html @@ -0,0 +1,5 @@ + + +This intention replaces 'substring' and 'indexOf' calls with 'substringAfter' call. + + diff --git a/idea/resources/intentionDescriptions/ReplaceSubstringWithSubstringBeforeIntention/after.kt.template b/idea/resources/intentionDescriptions/ReplaceSubstringWithSubstringBeforeIntention/after.kt.template new file mode 100644 index 00000000000..730dca58149 --- /dev/null +++ b/idea/resources/intentionDescriptions/ReplaceSubstringWithSubstringBeforeIntention/after.kt.template @@ -0,0 +1 @@ +s.substringBefore('x') \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/ReplaceSubstringWithSubstringBeforeIntention/before.kt.template b/idea/resources/intentionDescriptions/ReplaceSubstringWithSubstringBeforeIntention/before.kt.template new file mode 100644 index 00000000000..dccf11d3f0a --- /dev/null +++ b/idea/resources/intentionDescriptions/ReplaceSubstringWithSubstringBeforeIntention/before.kt.template @@ -0,0 +1 @@ +s.substring(0, s.indexOf('x')) \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/ReplaceSubstringWithSubstringBeforeIntention/description.html b/idea/resources/intentionDescriptions/ReplaceSubstringWithSubstringBeforeIntention/description.html new file mode 100644 index 00000000000..27730fc21a0 --- /dev/null +++ b/idea/resources/intentionDescriptions/ReplaceSubstringWithSubstringBeforeIntention/description.html @@ -0,0 +1,5 @@ + + +This intention replaces 'substring' and 'indexOf' calls with 'substringBefore' call. + + diff --git a/idea/resources/intentionDescriptions/ReplaceSubstringWithTakeIntention/after.kt.template b/idea/resources/intentionDescriptions/ReplaceSubstringWithTakeIntention/after.kt.template new file mode 100644 index 00000000000..0af9fef8882 --- /dev/null +++ b/idea/resources/intentionDescriptions/ReplaceSubstringWithTakeIntention/after.kt.template @@ -0,0 +1 @@ +s.take(10) \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/ReplaceSubstringWithTakeIntention/before.kt.template b/idea/resources/intentionDescriptions/ReplaceSubstringWithTakeIntention/before.kt.template new file mode 100644 index 00000000000..d6b5230cc5a --- /dev/null +++ b/idea/resources/intentionDescriptions/ReplaceSubstringWithTakeIntention/before.kt.template @@ -0,0 +1 @@ +s.substring(0, 10) \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/ReplaceSubstringWithTakeIntention/description.html b/idea/resources/intentionDescriptions/ReplaceSubstringWithTakeIntention/description.html new file mode 100644 index 00000000000..f5863546a1a --- /dev/null +++ b/idea/resources/intentionDescriptions/ReplaceSubstringWithTakeIntention/description.html @@ -0,0 +1,5 @@ + + +This intention replaces 'substring' call with 'take' call. + + diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 6c1a109ddb2..3ff4756c90d 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -880,6 +880,31 @@ Kotlin + + org.jetbrains.kotlin.idea.intentions.ReplaceSubstringWithDropIntention + Kotlin + + + + org.jetbrains.kotlin.idea.intentions.ReplaceSubstringWithDropLastIntention + Kotlin + + + + org.jetbrains.kotlin.idea.intentions.ReplaceSubstringWithSubstringAfterIntention + Kotlin + + + + org.jetbrains.kotlin.idea.intentions.ReplaceSubstringWithSubstringBeforeIntention + Kotlin + + + + org.jetbrains.kotlin.idea.intentions.ReplaceSubstringWithTakeIntention + Kotlin + + org.jetbrains.kotlin.idea.intentions.RemoveBracesIntention Kotlin diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSubstringIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSubstringIntention.kt new file mode 100644 index 00000000000..49ac34766dc --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSubstringIntention.kt @@ -0,0 +1,52 @@ +/* + * 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 + +import com.intellij.codeInsight.intention.HighPriorityAction +import com.intellij.openapi.util.TextRange +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isStableVariable +import org.jetbrains.kotlin.psi.KtConstantExpression +import org.jetbrains.kotlin.psi.KtDotQualifiedExpression +import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode + +abstract class ReplaceSubstringIntention(text: String) : SelfTargetingRangeIntention(KtDotQualifiedExpression::class.java, text), HighPriorityAction { + protected abstract fun applicabilityRangeInner(element: KtDotQualifiedExpression): TextRange? + + override fun applicabilityRange(element: KtDotQualifiedExpression): TextRange? { + if (!element.receiverExpression.isStableVariable()) return null + val resolvedCall = element.toResolvedCall(BodyResolveMode.PARTIAL) ?: return null + if (resolvedCall.resultingDescriptor.fqNameUnsafe.asString() != "kotlin.text.substring") return null + return applicabilityRangeInner(element) + } + + protected fun KtDotQualifiedExpression.isFirstArgumentZero(): Boolean { + val resolvedCall = toResolvedCall(BodyResolveMode.PARTIAL) ?: return false + val expression = resolvedCall.call.valueArguments[0].getArgumentExpression() as? KtConstantExpression ?: return false + + val bindingContext = expression.analyze() + val constant = ConstantExpressionEvaluator.getConstant(expression, bindingContext) ?: return false + val constantType = bindingContext.getType(expression) ?: return false + return constant.getValue(constantType) == 0 + } + + protected fun getTextRange(element: KtDotQualifiedExpression): TextRange? { + return element.callExpression!!.textRange + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSubstringWithDropIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSubstringWithDropIntention.kt new file mode 100644 index 00000000000..0666991176c --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSubstringWithDropIntention.kt @@ -0,0 +1,39 @@ +/* + * 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 + +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.util.TextRange +import org.jetbrains.kotlin.psi.KtDotQualifiedExpression +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.psi.createExpressionByPattern + +class ReplaceSubstringWithDropIntention : ReplaceSubstringIntention("Replace 'substring' call with 'drop' call") { + override fun applicabilityRangeInner(element: KtDotQualifiedExpression): TextRange? { + val arguments = element.callExpression?.valueArguments ?: return null + if (arguments.count() != 1) return null + return getTextRange(element) + } + + override fun applyTo(element: KtDotQualifiedExpression, editor: Editor?) { + val receiver = element.receiverExpression + val argument = element.callExpression!!.valueArguments[0].getArgumentExpression()!! + + val psiFactory = KtPsiFactory(element) + element.replace(psiFactory.createExpressionByPattern("$0.drop($1)", receiver, argument)) + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSubstringWithDropLastIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSubstringWithDropLastIntention.kt new file mode 100644 index 00000000000..0f6ace5f314 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSubstringWithDropLastIntention.kt @@ -0,0 +1,55 @@ +/* + * 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 + +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.util.TextRange +import org.jetbrains.kotlin.idea.intentions.branchedTransformations.evaluatesTo +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.* + +class ReplaceSubstringWithDropLastIntention : ReplaceSubstringIntention("Replace 'substring' call with 'dropLast' call") { + override fun applicabilityRangeInner(element: KtDotQualifiedExpression): TextRange? { + val arguments = element.callExpression?.valueArguments ?: return null + if (arguments.count() != 2 || !element.isFirstArgumentZero()) return null + + val secondArgumentExpression = arguments[1].getArgumentExpression() + + if (secondArgumentExpression !is KtBinaryExpression) return null + if (secondArgumentExpression.operationReference.getReferencedNameElementType() != KtTokens.MINUS) return null + if (isLengthAccess(secondArgumentExpression.left, element.receiverExpression)) { + return getTextRange(element) + } + + return null + } + + override fun applyTo(element: KtDotQualifiedExpression, editor: Editor?) { + val receiver = element.receiverExpression + val argument = element.callExpression!!.valueArguments[1].getArgumentExpression()!! + val rightExpression = (argument as KtBinaryExpression).right!! + + val psiFactory = KtPsiFactory(element) + element.replace(psiFactory.createExpressionByPattern("$0.dropLast($1)", receiver, rightExpression)) + } + + private fun isLengthAccess(expression: KtExpression?, expectedReceiver: KtExpression): Boolean { + return expression is KtDotQualifiedExpression + && expression.selectorExpression.let { it is KtNameReferenceExpression && it.getReferencedName() == "length" } + && expression.receiverExpression.evaluatesTo(expectedReceiver) + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSubstringWithSubstringBeforeAfterIntentions.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSubstringWithSubstringBeforeAfterIntentions.kt new file mode 100644 index 00000000000..d749bf77fe1 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSubstringWithSubstringBeforeAfterIntentions.kt @@ -0,0 +1,81 @@ +/* + * 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 + +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.util.TextRange +import org.jetbrains.kotlin.idea.intentions.branchedTransformations.evaluatesTo +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode + +class ReplaceSubstringWithSubstringAfterIntention : ReplaceSubstringIntention("Replace 'substring' call with 'substringAfter' call") { + override fun applicabilityRangeInner(element: KtDotQualifiedExpression): TextRange? { + val arguments = element.callExpression?.valueArguments ?: return null + + if (arguments.count() == 1 && isIndexOfCall(arguments[0].getArgumentExpression(), element.receiverExpression)) { + return getTextRange(element) + } + + return null + } + + override fun applyTo(element: KtDotQualifiedExpression, editor: Editor?) { + element.replaceWith( + "$0.substringAfter($1)", + (element.getArgumentExpression(0) as KtDotQualifiedExpression).getArgumentExpression(0)) + } +} + +class ReplaceSubstringWithSubstringBeforeIntention : ReplaceSubstringIntention("Replace 'substring' call with 'substringBefore' call") { + override fun applicabilityRangeInner(element: KtDotQualifiedExpression): TextRange? { + val arguments = element.callExpression?.valueArguments ?: return null + + if (arguments.count() == 2 + && element.isFirstArgumentZero() + && isIndexOfCall(arguments[1].getArgumentExpression(), element.receiverExpression)) { + return getTextRange(element) + } + + return null + } + + override fun applyTo(element: KtDotQualifiedExpression, editor: Editor?) { + element.replaceWith( + "$0.substringBefore($1)", + (element.getArgumentExpression(1) as KtDotQualifiedExpression).getArgumentExpression(0)) + } +} + + +private fun KtDotQualifiedExpression.replaceWith(pattern: String, argument: KtExpression) { + val psiFactory = KtPsiFactory(this) + replace(psiFactory.createExpressionByPattern(pattern, receiverExpression, argument)) +} + +private fun KtDotQualifiedExpression.getArgumentExpression(index: Int): KtExpression { + return callExpression!!.valueArguments[index].getArgumentExpression()!! +} + +private fun isIndexOfCall(expression: KtExpression?, expectedReceiver: KtExpression): Boolean { + if (expression !is KtDotQualifiedExpression) return false + val resolvedCall = expression.toResolvedCall(BodyResolveMode.PARTIAL) ?: return false + if (resolvedCall.resultingDescriptor.fqNameUnsafe.asString() != "kotlin.text.indexOf") return false + if (!expression.receiverExpression.evaluatesTo(expectedReceiver)) return false + return expression.callExpression!!.valueArguments.count() == 1 +} + diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSubstringWithTakeIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSubstringWithTakeIntention.kt new file mode 100644 index 00000000000..58c686867d9 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSubstringWithTakeIntention.kt @@ -0,0 +1,41 @@ +/* + * 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 + +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.util.TextRange +import org.jetbrains.kotlin.psi.KtDotQualifiedExpression +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.psi.createExpressionByPattern + +class ReplaceSubstringWithTakeIntention : ReplaceSubstringIntention("Replace 'substring' call with 'take' call") { + override fun applicabilityRangeInner(element: KtDotQualifiedExpression): TextRange? { + val arguments = element.callExpression?.valueArguments ?: return null + if (arguments.count() == 2 && element.isFirstArgumentZero()) { + return getTextRange(element) + } + return null + } + + override fun applyTo(element: KtDotQualifiedExpression, editor: Editor?) { + val receiver = element.receiverExpression + val argument = element.callExpression!!.valueArguments[1].getArgumentExpression()!! + + val psiFactory = KtPsiFactory(element) + element.replace(psiFactory.createExpressionByPattern("$0.take($1)", receiver, argument)) + } +} diff --git a/idea/testData/intentions/replaceSubstringWithDrop/.intention b/idea/testData/intentions/replaceSubstringWithDrop/.intention new file mode 100644 index 00000000000..3dd0f221200 --- /dev/null +++ b/idea/testData/intentions/replaceSubstringWithDrop/.intention @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.intentions.ReplaceSubstringWithDropIntention \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithDrop/replaceWithDrop.kt b/idea/testData/intentions/replaceSubstringWithDrop/replaceWithDrop.kt new file mode 100644 index 00000000000..d0c031db4ed --- /dev/null +++ b/idea/testData/intentions/replaceSubstringWithDrop/replaceWithDrop.kt @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +fun foo(s: String) { + s.substring(4) +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithDrop/replaceWithDrop.kt.after b/idea/testData/intentions/replaceSubstringWithDrop/replaceWithDrop.kt.after new file mode 100644 index 00000000000..5d3f38ae406 --- /dev/null +++ b/idea/testData/intentions/replaceSubstringWithDrop/replaceWithDrop.kt.after @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +fun foo(s: String) { + s.drop(4) +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithDrop/semicolon.kt b/idea/testData/intentions/replaceSubstringWithDrop/semicolon.kt new file mode 100644 index 00000000000..3b682cb4190 --- /dev/null +++ b/idea/testData/intentions/replaceSubstringWithDrop/semicolon.kt @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +fun foo(s: String) { + s.substring(4); +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithDrop/semicolon.kt.after b/idea/testData/intentions/replaceSubstringWithDrop/semicolon.kt.after new file mode 100644 index 00000000000..9a0b9134d6b --- /dev/null +++ b/idea/testData/intentions/replaceSubstringWithDrop/semicolon.kt.after @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +fun foo(s: String) { + s.drop(4); +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithDrop/substringWithTwoArguments.kt b/idea/testData/intentions/replaceSubstringWithDrop/substringWithTwoArguments.kt new file mode 100644 index 00000000000..f791c078b76 --- /dev/null +++ b/idea/testData/intentions/replaceSubstringWithDrop/substringWithTwoArguments.kt @@ -0,0 +1,6 @@ +// IS_APPLICABLE: false +// WITH_RUNTIME + +fun foo(s: String) { + s.substring(0, 10) +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithDropLast/.intention b/idea/testData/intentions/replaceSubstringWithDropLast/.intention new file mode 100644 index 00000000000..d1b3ab76c5d --- /dev/null +++ b/idea/testData/intentions/replaceSubstringWithDropLast/.intention @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.intentions.ReplaceSubstringWithDropLastIntention \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithDropLast/immutableProperty.kt b/idea/testData/intentions/replaceSubstringWithDropLast/immutableProperty.kt new file mode 100644 index 00000000000..239148a1f07 --- /dev/null +++ b/idea/testData/intentions/replaceSubstringWithDropLast/immutableProperty.kt @@ -0,0 +1,7 @@ +// WITH_RUNTIME + +class A(val x: String) + +fun foo(a: A) { + a.x.substring(0, a.x.length - 5) +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithDropLast/immutableProperty.kt.after b/idea/testData/intentions/replaceSubstringWithDropLast/immutableProperty.kt.after new file mode 100644 index 00000000000..392ea5f9a2d --- /dev/null +++ b/idea/testData/intentions/replaceSubstringWithDropLast/immutableProperty.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME + +class A(val x: String) + +fun foo(a: A) { + a.x.dropLast(5) +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithDropLast/nonZeroFirstArgument.kt b/idea/testData/intentions/replaceSubstringWithDropLast/nonZeroFirstArgument.kt new file mode 100644 index 00000000000..de78eac2f86 --- /dev/null +++ b/idea/testData/intentions/replaceSubstringWithDropLast/nonZeroFirstArgument.kt @@ -0,0 +1,6 @@ +// IS_APPLICABLE: false +// WITH_RUNTIME + +fun foo(s: String) { + s.substring(3, s.length - 5) +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithDropLast/replaceWithDropLast.kt b/idea/testData/intentions/replaceSubstringWithDropLast/replaceWithDropLast.kt new file mode 100644 index 00000000000..0ffa4ecb25d --- /dev/null +++ b/idea/testData/intentions/replaceSubstringWithDropLast/replaceWithDropLast.kt @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +fun foo(s: String) { + s.substring(0, s.length - 5) +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithDropLast/replaceWithDropLast.kt.after b/idea/testData/intentions/replaceSubstringWithDropLast/replaceWithDropLast.kt.after new file mode 100644 index 00000000000..02014335dcd --- /dev/null +++ b/idea/testData/intentions/replaceSubstringWithDropLast/replaceWithDropLast.kt.after @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +fun foo(s: String) { + s.dropLast(5) +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithDropLast/semicolon.kt b/idea/testData/intentions/replaceSubstringWithDropLast/semicolon.kt new file mode 100644 index 00000000000..f55d37f1ff9 --- /dev/null +++ b/idea/testData/intentions/replaceSubstringWithDropLast/semicolon.kt @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +fun foo(s: String) { + s.substring(0, s.length - 5); +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithDropLast/semicolon.kt.after b/idea/testData/intentions/replaceSubstringWithDropLast/semicolon.kt.after new file mode 100644 index 00000000000..5075cc8eedd --- /dev/null +++ b/idea/testData/intentions/replaceSubstringWithDropLast/semicolon.kt.after @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +fun foo(s: String) { + s.dropLast(5); +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithSubstringAfter/.intention b/idea/testData/intentions/replaceSubstringWithSubstringAfter/.intention new file mode 100644 index 00000000000..4bf5c8420bc --- /dev/null +++ b/idea/testData/intentions/replaceSubstringWithSubstringAfter/.intention @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.intentions.ReplaceSubstringWithSubstringAfterIntention \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithSubstringAfter/immutableProperty.kt b/idea/testData/intentions/replaceSubstringWithSubstringAfter/immutableProperty.kt new file mode 100644 index 00000000000..eb901ec3f06 --- /dev/null +++ b/idea/testData/intentions/replaceSubstringWithSubstringAfter/immutableProperty.kt @@ -0,0 +1,7 @@ +// WITH_RUNTIME + +class A(val x: String) + +fun foo(a: A) { + a.x.substring(a.x.indexOf('x')) +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithSubstringAfter/immutableProperty.kt.after b/idea/testData/intentions/replaceSubstringWithSubstringAfter/immutableProperty.kt.after new file mode 100644 index 00000000000..dee51ffa838 --- /dev/null +++ b/idea/testData/intentions/replaceSubstringWithSubstringAfter/immutableProperty.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME + +class A(val x: String) + +fun foo(a: A) { + a.x.substringAfter('x') +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithSubstringAfter/replaceWithSubstringAfter.kt b/idea/testData/intentions/replaceSubstringWithSubstringAfter/replaceWithSubstringAfter.kt new file mode 100644 index 00000000000..8741f1d75a2 --- /dev/null +++ b/idea/testData/intentions/replaceSubstringWithSubstringAfter/replaceWithSubstringAfter.kt @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +fun foo(s: String) { + s.substring(s.indexOf('x')) +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithSubstringAfter/replaceWithSubstringAfter.kt.after b/idea/testData/intentions/replaceSubstringWithSubstringAfter/replaceWithSubstringAfter.kt.after new file mode 100644 index 00000000000..fc90eb8a225 --- /dev/null +++ b/idea/testData/intentions/replaceSubstringWithSubstringAfter/replaceWithSubstringAfter.kt.after @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +fun foo(s: String) { + s.substringAfter('x') +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithSubstringAfter/semicolon.kt b/idea/testData/intentions/replaceSubstringWithSubstringAfter/semicolon.kt new file mode 100644 index 00000000000..d3ec7c278b2 --- /dev/null +++ b/idea/testData/intentions/replaceSubstringWithSubstringAfter/semicolon.kt @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +fun foo(s: String) { + s.substring(s.indexOf('x')); +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithSubstringAfter/semicolon.kt.after b/idea/testData/intentions/replaceSubstringWithSubstringAfter/semicolon.kt.after new file mode 100644 index 00000000000..34a665b72ec --- /dev/null +++ b/idea/testData/intentions/replaceSubstringWithSubstringAfter/semicolon.kt.after @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +fun foo(s: String) { + s.substringAfter('x'); +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithSubstringBefore/.intention b/idea/testData/intentions/replaceSubstringWithSubstringBefore/.intention new file mode 100644 index 00000000000..d65912abba3 --- /dev/null +++ b/idea/testData/intentions/replaceSubstringWithSubstringBefore/.intention @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.intentions.ReplaceSubstringWithSubstringBeforeIntention \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithSubstringBefore/immutableProperty.kt b/idea/testData/intentions/replaceSubstringWithSubstringBefore/immutableProperty.kt new file mode 100644 index 00000000000..a2c836ecd17 --- /dev/null +++ b/idea/testData/intentions/replaceSubstringWithSubstringBefore/immutableProperty.kt @@ -0,0 +1,7 @@ +// WITH_RUNTIME + +class A(val x: String) + +fun foo(a: A) { + a.x.substring(0, a.x.indexOf('x')) +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithSubstringBefore/immutableProperty.kt.after b/idea/testData/intentions/replaceSubstringWithSubstringBefore/immutableProperty.kt.after new file mode 100644 index 00000000000..d62fe8489a0 --- /dev/null +++ b/idea/testData/intentions/replaceSubstringWithSubstringBefore/immutableProperty.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME + +class A(val x: String) + +fun foo(a: A) { + a.x.substringBefore('x') +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithSubstringBefore/nonZeroFirstArgument.kt b/idea/testData/intentions/replaceSubstringWithSubstringBefore/nonZeroFirstArgument.kt new file mode 100644 index 00000000000..ddce1dcb825 --- /dev/null +++ b/idea/testData/intentions/replaceSubstringWithSubstringBefore/nonZeroFirstArgument.kt @@ -0,0 +1,6 @@ +// IS_APPLICABLE: false +// WITH_RUNTIME + +fun foo(s: String) { + s.substring(1, s.indexOf('x')) +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithSubstringBefore/replaceWithSubstringBefore.kt b/idea/testData/intentions/replaceSubstringWithSubstringBefore/replaceWithSubstringBefore.kt new file mode 100644 index 00000000000..aa71dbf3c63 --- /dev/null +++ b/idea/testData/intentions/replaceSubstringWithSubstringBefore/replaceWithSubstringBefore.kt @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +fun foo(s: String) { + s.substring(0, s.indexOf('x')) +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithSubstringBefore/replaceWithSubstringBefore.kt.after b/idea/testData/intentions/replaceSubstringWithSubstringBefore/replaceWithSubstringBefore.kt.after new file mode 100644 index 00000000000..02fddc3e408 --- /dev/null +++ b/idea/testData/intentions/replaceSubstringWithSubstringBefore/replaceWithSubstringBefore.kt.after @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +fun foo(s: String) { + s.substringBefore('x') +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithSubstringBefore/semicolon.kt b/idea/testData/intentions/replaceSubstringWithSubstringBefore/semicolon.kt new file mode 100644 index 00000000000..bb6bf84606f --- /dev/null +++ b/idea/testData/intentions/replaceSubstringWithSubstringBefore/semicolon.kt @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +fun foo(s: String) { + s.substring(0, s.indexOf('x')); +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithSubstringBefore/semicolon.kt.after b/idea/testData/intentions/replaceSubstringWithSubstringBefore/semicolon.kt.after new file mode 100644 index 00000000000..d4a32618a91 --- /dev/null +++ b/idea/testData/intentions/replaceSubstringWithSubstringBefore/semicolon.kt.after @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +fun foo(s: String) { + s.substringBefore('x'); +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithTake/.intention b/idea/testData/intentions/replaceSubstringWithTake/.intention new file mode 100644 index 00000000000..8cc60dcc852 --- /dev/null +++ b/idea/testData/intentions/replaceSubstringWithTake/.intention @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.intentions.ReplaceSubstringWithTakeIntention \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithTake/constantAsFirstArgument.kt b/idea/testData/intentions/replaceSubstringWithTake/constantAsFirstArgument.kt new file mode 100644 index 00000000000..5a69215aebc --- /dev/null +++ b/idea/testData/intentions/replaceSubstringWithTake/constantAsFirstArgument.kt @@ -0,0 +1,8 @@ +// IS_APPLICABLE: false +// WITH_RUNTIME + +const val x = 0 + +fun foo(s: String) { + s.substring(x, 10) +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithTake/expressionAsFirstArgument.kt b/idea/testData/intentions/replaceSubstringWithTake/expressionAsFirstArgument.kt new file mode 100644 index 00000000000..36e5ccf7af2 --- /dev/null +++ b/idea/testData/intentions/replaceSubstringWithTake/expressionAsFirstArgument.kt @@ -0,0 +1,6 @@ +// IS_APPLICABLE: false +// WITH_RUNTIME + +fun foo(s: String) { + s.substring(1 - 1, 10) +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithTake/nonZeroFirstArgument.kt b/idea/testData/intentions/replaceSubstringWithTake/nonZeroFirstArgument.kt new file mode 100644 index 00000000000..eb6a50f3fea --- /dev/null +++ b/idea/testData/intentions/replaceSubstringWithTake/nonZeroFirstArgument.kt @@ -0,0 +1,6 @@ +// IS_APPLICABLE: false +// WITH_RUNTIME + +fun foo(s: String) { + s.substring(1, 10) +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithTake/replaceWithTake.kt b/idea/testData/intentions/replaceSubstringWithTake/replaceWithTake.kt new file mode 100644 index 00000000000..36576de6eb5 --- /dev/null +++ b/idea/testData/intentions/replaceSubstringWithTake/replaceWithTake.kt @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +fun foo(s: String) { + s.substring(0, 10) +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithTake/replaceWithTake.kt.after b/idea/testData/intentions/replaceSubstringWithTake/replaceWithTake.kt.after new file mode 100644 index 00000000000..07a0f6f6adf --- /dev/null +++ b/idea/testData/intentions/replaceSubstringWithTake/replaceWithTake.kt.after @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +fun foo(s: String) { + s.take(10) +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithTake/semicolon.kt b/idea/testData/intentions/replaceSubstringWithTake/semicolon.kt new file mode 100644 index 00000000000..d0b7014b677 --- /dev/null +++ b/idea/testData/intentions/replaceSubstringWithTake/semicolon.kt @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +fun foo(s: String) { + s.substring(0, 10); +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithTake/semicolon.kt.after b/idea/testData/intentions/replaceSubstringWithTake/semicolon.kt.after new file mode 100644 index 00000000000..1a3931dd92b --- /dev/null +++ b/idea/testData/intentions/replaceSubstringWithTake/semicolon.kt.after @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +fun foo(s: String) { + s.take(10); +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index 88c5080457c..f3c9fed4608 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -7573,6 +7573,165 @@ public class IntentionTestGenerated extends AbstractIntentionTest { } } + @TestMetadata("idea/testData/intentions/replaceSubstringWithDrop") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ReplaceSubstringWithDrop extends AbstractIntentionTest { + public void testAllFilesPresentInReplaceSubstringWithDrop() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/replaceSubstringWithDrop"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true); + } + + @TestMetadata("replaceWithDrop.kt") + public void testReplaceWithDrop() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSubstringWithDrop/replaceWithDrop.kt"); + doTest(fileName); + } + + @TestMetadata("semicolon.kt") + public void testSemicolon() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSubstringWithDrop/semicolon.kt"); + doTest(fileName); + } + + @TestMetadata("substringWithTwoArguments.kt") + public void testSubstringWithTwoArguments() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSubstringWithDrop/substringWithTwoArguments.kt"); + doTest(fileName); + } + } + + @TestMetadata("idea/testData/intentions/replaceSubstringWithDropLast") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ReplaceSubstringWithDropLast extends AbstractIntentionTest { + public void testAllFilesPresentInReplaceSubstringWithDropLast() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/replaceSubstringWithDropLast"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true); + } + + @TestMetadata("immutableProperty.kt") + public void testImmutableProperty() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSubstringWithDropLast/immutableProperty.kt"); + doTest(fileName); + } + + @TestMetadata("nonZeroFirstArgument.kt") + public void testNonZeroFirstArgument() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSubstringWithDropLast/nonZeroFirstArgument.kt"); + doTest(fileName); + } + + @TestMetadata("replaceWithDropLast.kt") + public void testReplaceWithDropLast() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSubstringWithDropLast/replaceWithDropLast.kt"); + doTest(fileName); + } + + @TestMetadata("semicolon.kt") + public void testSemicolon() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSubstringWithDropLast/semicolon.kt"); + doTest(fileName); + } + } + + @TestMetadata("idea/testData/intentions/replaceSubstringWithSubstringAfter") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ReplaceSubstringWithSubstringAfter extends AbstractIntentionTest { + public void testAllFilesPresentInReplaceSubstringWithSubstringAfter() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/replaceSubstringWithSubstringAfter"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true); + } + + @TestMetadata("immutableProperty.kt") + public void testImmutableProperty() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSubstringWithSubstringAfter/immutableProperty.kt"); + doTest(fileName); + } + + @TestMetadata("replaceWithSubstringAfter.kt") + public void testReplaceWithSubstringAfter() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSubstringWithSubstringAfter/replaceWithSubstringAfter.kt"); + doTest(fileName); + } + + @TestMetadata("semicolon.kt") + public void testSemicolon() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSubstringWithSubstringAfter/semicolon.kt"); + doTest(fileName); + } + } + + @TestMetadata("idea/testData/intentions/replaceSubstringWithSubstringBefore") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ReplaceSubstringWithSubstringBefore extends AbstractIntentionTest { + public void testAllFilesPresentInReplaceSubstringWithSubstringBefore() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/replaceSubstringWithSubstringBefore"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true); + } + + @TestMetadata("immutableProperty.kt") + public void testImmutableProperty() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSubstringWithSubstringBefore/immutableProperty.kt"); + doTest(fileName); + } + + @TestMetadata("nonZeroFirstArgument.kt") + public void testNonZeroFirstArgument() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSubstringWithSubstringBefore/nonZeroFirstArgument.kt"); + doTest(fileName); + } + + @TestMetadata("replaceWithSubstringBefore.kt") + public void testReplaceWithSubstringBefore() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSubstringWithSubstringBefore/replaceWithSubstringBefore.kt"); + doTest(fileName); + } + + @TestMetadata("semicolon.kt") + public void testSemicolon() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSubstringWithSubstringBefore/semicolon.kt"); + doTest(fileName); + } + } + + @TestMetadata("idea/testData/intentions/replaceSubstringWithTake") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ReplaceSubstringWithTake extends AbstractIntentionTest { + public void testAllFilesPresentInReplaceSubstringWithTake() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/replaceSubstringWithTake"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true); + } + + @TestMetadata("constantAsFirstArgument.kt") + public void testConstantAsFirstArgument() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSubstringWithTake/constantAsFirstArgument.kt"); + doTest(fileName); + } + + @TestMetadata("expressionAsFirstArgument.kt") + public void testExpressionAsFirstArgument() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSubstringWithTake/expressionAsFirstArgument.kt"); + doTest(fileName); + } + + @TestMetadata("nonZeroFirstArgument.kt") + public void testNonZeroFirstArgument() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSubstringWithTake/nonZeroFirstArgument.kt"); + doTest(fileName); + } + + @TestMetadata("replaceWithTake.kt") + public void testReplaceWithTake() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSubstringWithTake/replaceWithTake.kt"); + doTest(fileName); + } + + @TestMetadata("semicolon.kt") + public void testSemicolon() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSubstringWithTake/semicolon.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/testData/intentions/replaceWithOperatorAssignment") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) From b2e98e9353f097cf6464b0019bae0806d2f39c0a Mon Sep 17 00:00:00 2001 From: Anton Sukhonosenko Date: Thu, 14 Jan 2016 22:40:50 +0300 Subject: [PATCH 2/2] Suggest to replace 'substring' calls by take/drop/dropLast calls when possible Code review fixes #KT-10196 Fixed --- .../after.kt.template | 1 - .../before.kt.template | 1 - .../description.html | 5 --- .../description.html | 2 +- .../description.html | 2 +- .../description.html | 2 +- .../description.html | 2 +- idea/src/META-INF/plugin.xml | 5 --- .../intentions/ReplaceSubstringIntention.kt | 41 ++++++++++++----- .../ReplaceSubstringWithDropIntention.kt | 39 ---------------- .../ReplaceSubstringWithDropLastIntention.kt | 6 +-- ...tringWithSubstringBeforeAfterIntentions.kt | 19 +------- .../ReplaceSubstringWithTakeIntention.kt | 7 +-- .../replaceSubstringWithDrop/.intention | 1 - .../replaceWithDrop.kt | 5 --- .../replaceWithDrop.kt.after | 5 --- .../replaceSubstringWithDrop/semicolon.kt | 5 --- .../semicolon.kt.after | 5 --- .../substringWithTwoArguments.kt | 6 --- .../methodCallReceiver.kt | 10 +++++ .../methodCallReceiver.kt | 10 +++++ .../methodCallReceiver.kt | 10 +++++ .../intentions/IntentionTestGenerated.java | 45 ++++++++----------- 23 files changed, 87 insertions(+), 147 deletions(-) delete mode 100644 idea/resources/intentionDescriptions/ReplaceSubstringWithDropIntention/after.kt.template delete mode 100644 idea/resources/intentionDescriptions/ReplaceSubstringWithDropIntention/before.kt.template delete mode 100644 idea/resources/intentionDescriptions/ReplaceSubstringWithDropIntention/description.html delete mode 100644 idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSubstringWithDropIntention.kt delete mode 100644 idea/testData/intentions/replaceSubstringWithDrop/.intention delete mode 100644 idea/testData/intentions/replaceSubstringWithDrop/replaceWithDrop.kt delete mode 100644 idea/testData/intentions/replaceSubstringWithDrop/replaceWithDrop.kt.after delete mode 100644 idea/testData/intentions/replaceSubstringWithDrop/semicolon.kt delete mode 100644 idea/testData/intentions/replaceSubstringWithDrop/semicolon.kt.after delete mode 100644 idea/testData/intentions/replaceSubstringWithDrop/substringWithTwoArguments.kt create mode 100644 idea/testData/intentions/replaceSubstringWithDropLast/methodCallReceiver.kt create mode 100644 idea/testData/intentions/replaceSubstringWithSubstringAfter/methodCallReceiver.kt create mode 100644 idea/testData/intentions/replaceSubstringWithSubstringBefore/methodCallReceiver.kt diff --git a/idea/resources/intentionDescriptions/ReplaceSubstringWithDropIntention/after.kt.template b/idea/resources/intentionDescriptions/ReplaceSubstringWithDropIntention/after.kt.template deleted file mode 100644 index bc7391ca86c..00000000000 --- a/idea/resources/intentionDescriptions/ReplaceSubstringWithDropIntention/after.kt.template +++ /dev/null @@ -1 +0,0 @@ -s.drop(5) \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/ReplaceSubstringWithDropIntention/before.kt.template b/idea/resources/intentionDescriptions/ReplaceSubstringWithDropIntention/before.kt.template deleted file mode 100644 index 6ede901326c..00000000000 --- a/idea/resources/intentionDescriptions/ReplaceSubstringWithDropIntention/before.kt.template +++ /dev/null @@ -1 +0,0 @@ -s.substring(5) \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/ReplaceSubstringWithDropIntention/description.html b/idea/resources/intentionDescriptions/ReplaceSubstringWithDropIntention/description.html deleted file mode 100644 index df9b27cfaa7..00000000000 --- a/idea/resources/intentionDescriptions/ReplaceSubstringWithDropIntention/description.html +++ /dev/null @@ -1,5 +0,0 @@ - - -This intention replaces 'substring' call with 'drop' call. - - diff --git a/idea/resources/intentionDescriptions/ReplaceSubstringWithDropLastIntention/description.html b/idea/resources/intentionDescriptions/ReplaceSubstringWithDropLastIntention/description.html index 40ed08cd378..7482e9e2132 100644 --- a/idea/resources/intentionDescriptions/ReplaceSubstringWithDropLastIntention/description.html +++ b/idea/resources/intentionDescriptions/ReplaceSubstringWithDropLastIntention/description.html @@ -1,5 +1,5 @@ -This intention replaces 'substring' call with 'dropLast' call. +This intention replaces call like s.substring(0, s.length - x) with s.dropLast(x) call. diff --git a/idea/resources/intentionDescriptions/ReplaceSubstringWithSubstringAfterIntention/description.html b/idea/resources/intentionDescriptions/ReplaceSubstringWithSubstringAfterIntention/description.html index 883344cd71d..93f3fe3f69c 100644 --- a/idea/resources/intentionDescriptions/ReplaceSubstringWithSubstringAfterIntention/description.html +++ b/idea/resources/intentionDescriptions/ReplaceSubstringWithSubstringAfterIntention/description.html @@ -1,5 +1,5 @@ -This intention replaces 'substring' and 'indexOf' calls with 'substringAfter' call. +This intention replaces call like s.substring(s.indexOf(x)) with s.substringAfter(x) call. diff --git a/idea/resources/intentionDescriptions/ReplaceSubstringWithSubstringBeforeIntention/description.html b/idea/resources/intentionDescriptions/ReplaceSubstringWithSubstringBeforeIntention/description.html index 27730fc21a0..18f291720ad 100644 --- a/idea/resources/intentionDescriptions/ReplaceSubstringWithSubstringBeforeIntention/description.html +++ b/idea/resources/intentionDescriptions/ReplaceSubstringWithSubstringBeforeIntention/description.html @@ -1,5 +1,5 @@ -This intention replaces 'substring' and 'indexOf' calls with 'substringBefore' call. +This intention replaces call like s.substring(0, s.indexOf(x)) with s.substringBefore(x) call. diff --git a/idea/resources/intentionDescriptions/ReplaceSubstringWithTakeIntention/description.html b/idea/resources/intentionDescriptions/ReplaceSubstringWithTakeIntention/description.html index f5863546a1a..a9d4286de2c 100644 --- a/idea/resources/intentionDescriptions/ReplaceSubstringWithTakeIntention/description.html +++ b/idea/resources/intentionDescriptions/ReplaceSubstringWithTakeIntention/description.html @@ -1,5 +1,5 @@ -This intention replaces 'substring' call with 'take' call. +This intention replaces call like s.substring(0, x) with s.take(x) call. diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 3ff4756c90d..ae9456d9d3e 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -880,11 +880,6 @@ Kotlin - - org.jetbrains.kotlin.idea.intentions.ReplaceSubstringWithDropIntention - Kotlin - - org.jetbrains.kotlin.idea.intentions.ReplaceSubstringWithDropLastIntention Kotlin diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSubstringIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSubstringIntention.kt index 49ac34766dc..3c39edc0de3 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSubstringIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSubstringIntention.kt @@ -16,37 +16,54 @@ package org.jetbrains.kotlin.idea.intentions -import com.intellij.codeInsight.intention.HighPriorityAction import com.intellij.openapi.util.TextRange import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.intentions.branchedTransformations.evaluatesTo import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isStableVariable -import org.jetbrains.kotlin.psi.KtConstantExpression -import org.jetbrains.kotlin.psi.KtDotQualifiedExpression +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode -abstract class ReplaceSubstringIntention(text: String) : SelfTargetingRangeIntention(KtDotQualifiedExpression::class.java, text), HighPriorityAction { +abstract class ReplaceSubstringIntention(text: String) : SelfTargetingRangeIntention(KtDotQualifiedExpression::class.java, text) { protected abstract fun applicabilityRangeInner(element: KtDotQualifiedExpression): TextRange? override fun applicabilityRange(element: KtDotQualifiedExpression): TextRange? { - if (!element.receiverExpression.isStableVariable()) return null - val resolvedCall = element.toResolvedCall(BodyResolveMode.PARTIAL) ?: return null - if (resolvedCall.resultingDescriptor.fqNameUnsafe.asString() != "kotlin.text.substring") return null - return applicabilityRangeInner(element) + if (element.receiverExpression.isStableVariable() && element.isMethodCall("kotlin.text.substring")) { + return applicabilityRangeInner(element) + } + return null + } + + protected fun isIndexOfCall(expression: KtExpression?, expectedReceiver: KtExpression): Boolean { + return expression is KtDotQualifiedExpression + && expression.isMethodCall("kotlin.text.indexOf") + && expression.receiverExpression.evaluatesTo(expectedReceiver) + && expression.callExpression!!.valueArguments.size == 1 + } + + private fun KtDotQualifiedExpression.isMethodCall(fqMethodName: String): Boolean { + val resolvedCall = toResolvedCall(BodyResolveMode.PARTIAL) ?: return false + return resolvedCall.resultingDescriptor.fqNameUnsafe.asString() == fqMethodName } protected fun KtDotQualifiedExpression.isFirstArgumentZero(): Boolean { - val resolvedCall = toResolvedCall(BodyResolveMode.PARTIAL) ?: return false + val bindingContext = analyze() + val resolvedCall = callExpression.getResolvedCall(bindingContext) ?: return false val expression = resolvedCall.call.valueArguments[0].getArgumentExpression() as? KtConstantExpression ?: return false - val bindingContext = expression.analyze() val constant = ConstantExpressionEvaluator.getConstant(expression, bindingContext) ?: return false val constantType = bindingContext.getType(expression) ?: return false return constant.getValue(constantType) == 0 } protected fun getTextRange(element: KtDotQualifiedExpression): TextRange? { - return element.callExpression!!.textRange + return element.callExpression?.textRange } -} + + protected fun KtDotQualifiedExpression.replaceWith(pattern: String, argument: KtExpression) { + val psiFactory = KtPsiFactory(this) + replace(psiFactory.createExpressionByPattern(pattern, receiverExpression, argument)) + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSubstringWithDropIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSubstringWithDropIntention.kt deleted file mode 100644 index 0666991176c..00000000000 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSubstringWithDropIntention.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2010-2016 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jetbrains.kotlin.idea.intentions - -import com.intellij.openapi.editor.Editor -import com.intellij.openapi.util.TextRange -import org.jetbrains.kotlin.psi.KtDotQualifiedExpression -import org.jetbrains.kotlin.psi.KtPsiFactory -import org.jetbrains.kotlin.psi.createExpressionByPattern - -class ReplaceSubstringWithDropIntention : ReplaceSubstringIntention("Replace 'substring' call with 'drop' call") { - override fun applicabilityRangeInner(element: KtDotQualifiedExpression): TextRange? { - val arguments = element.callExpression?.valueArguments ?: return null - if (arguments.count() != 1) return null - return getTextRange(element) - } - - override fun applyTo(element: KtDotQualifiedExpression, editor: Editor?) { - val receiver = element.receiverExpression - val argument = element.callExpression!!.valueArguments[0].getArgumentExpression()!! - - val psiFactory = KtPsiFactory(element) - element.replace(psiFactory.createExpressionByPattern("$0.drop($1)", receiver, argument)) - } -} diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSubstringWithDropLastIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSubstringWithDropLastIntention.kt index 0f6ace5f314..6a518392e6b 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSubstringWithDropLastIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSubstringWithDropLastIntention.kt @@ -25,7 +25,7 @@ import org.jetbrains.kotlin.psi.* class ReplaceSubstringWithDropLastIntention : ReplaceSubstringIntention("Replace 'substring' call with 'dropLast' call") { override fun applicabilityRangeInner(element: KtDotQualifiedExpression): TextRange? { val arguments = element.callExpression?.valueArguments ?: return null - if (arguments.count() != 2 || !element.isFirstArgumentZero()) return null + if (arguments.size != 2 || !element.isFirstArgumentZero()) return null val secondArgumentExpression = arguments[1].getArgumentExpression() @@ -39,12 +39,10 @@ class ReplaceSubstringWithDropLastIntention : ReplaceSubstringIntention("Replace } override fun applyTo(element: KtDotQualifiedExpression, editor: Editor?) { - val receiver = element.receiverExpression val argument = element.callExpression!!.valueArguments[1].getArgumentExpression()!! val rightExpression = (argument as KtBinaryExpression).right!! - val psiFactory = KtPsiFactory(element) - element.replace(psiFactory.createExpressionByPattern("$0.dropLast($1)", receiver, rightExpression)) + element.replaceWith("$0.dropLast($1)", rightExpression) } private fun isLengthAccess(expression: KtExpression?, expectedReceiver: KtExpression): Boolean { diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSubstringWithSubstringBeforeAfterIntentions.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSubstringWithSubstringBeforeAfterIntentions.kt index d749bf77fe1..73f65dc7aa0 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSubstringWithSubstringBeforeAfterIntentions.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSubstringWithSubstringBeforeAfterIntentions.kt @@ -27,7 +27,7 @@ class ReplaceSubstringWithSubstringAfterIntention : ReplaceSubstringIntention("R override fun applicabilityRangeInner(element: KtDotQualifiedExpression): TextRange? { val arguments = element.callExpression?.valueArguments ?: return null - if (arguments.count() == 1 && isIndexOfCall(arguments[0].getArgumentExpression(), element.receiverExpression)) { + if (arguments.size == 1 && isIndexOfCall(arguments[0].getArgumentExpression(), element.receiverExpression)) { return getTextRange(element) } @@ -45,7 +45,7 @@ class ReplaceSubstringWithSubstringBeforeIntention : ReplaceSubstringIntention(" override fun applicabilityRangeInner(element: KtDotQualifiedExpression): TextRange? { val arguments = element.callExpression?.valueArguments ?: return null - if (arguments.count() == 2 + if (arguments.size == 2 && element.isFirstArgumentZero() && isIndexOfCall(arguments[1].getArgumentExpression(), element.receiverExpression)) { return getTextRange(element) @@ -61,21 +61,6 @@ class ReplaceSubstringWithSubstringBeforeIntention : ReplaceSubstringIntention(" } } - -private fun KtDotQualifiedExpression.replaceWith(pattern: String, argument: KtExpression) { - val psiFactory = KtPsiFactory(this) - replace(psiFactory.createExpressionByPattern(pattern, receiverExpression, argument)) -} - private fun KtDotQualifiedExpression.getArgumentExpression(index: Int): KtExpression { return callExpression!!.valueArguments[index].getArgumentExpression()!! } - -private fun isIndexOfCall(expression: KtExpression?, expectedReceiver: KtExpression): Boolean { - if (expression !is KtDotQualifiedExpression) return false - val resolvedCall = expression.toResolvedCall(BodyResolveMode.PARTIAL) ?: return false - if (resolvedCall.resultingDescriptor.fqNameUnsafe.asString() != "kotlin.text.indexOf") return false - if (!expression.receiverExpression.evaluatesTo(expectedReceiver)) return false - return expression.callExpression!!.valueArguments.count() == 1 -} - diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSubstringWithTakeIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSubstringWithTakeIntention.kt index 58c686867d9..6e17dd7097c 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSubstringWithTakeIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSubstringWithTakeIntention.kt @@ -25,17 +25,14 @@ import org.jetbrains.kotlin.psi.createExpressionByPattern class ReplaceSubstringWithTakeIntention : ReplaceSubstringIntention("Replace 'substring' call with 'take' call") { override fun applicabilityRangeInner(element: KtDotQualifiedExpression): TextRange? { val arguments = element.callExpression?.valueArguments ?: return null - if (arguments.count() == 2 && element.isFirstArgumentZero()) { + if (arguments.size == 2 && element.isFirstArgumentZero()) { return getTextRange(element) } return null } override fun applyTo(element: KtDotQualifiedExpression, editor: Editor?) { - val receiver = element.receiverExpression val argument = element.callExpression!!.valueArguments[1].getArgumentExpression()!! - - val psiFactory = KtPsiFactory(element) - element.replace(psiFactory.createExpressionByPattern("$0.take($1)", receiver, argument)) + element.replaceWith("$0.take($1)", argument) } } diff --git a/idea/testData/intentions/replaceSubstringWithDrop/.intention b/idea/testData/intentions/replaceSubstringWithDrop/.intention deleted file mode 100644 index 3dd0f221200..00000000000 --- a/idea/testData/intentions/replaceSubstringWithDrop/.intention +++ /dev/null @@ -1 +0,0 @@ -org.jetbrains.kotlin.idea.intentions.ReplaceSubstringWithDropIntention \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithDrop/replaceWithDrop.kt b/idea/testData/intentions/replaceSubstringWithDrop/replaceWithDrop.kt deleted file mode 100644 index d0c031db4ed..00000000000 --- a/idea/testData/intentions/replaceSubstringWithDrop/replaceWithDrop.kt +++ /dev/null @@ -1,5 +0,0 @@ -// WITH_RUNTIME - -fun foo(s: String) { - s.substring(4) -} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithDrop/replaceWithDrop.kt.after b/idea/testData/intentions/replaceSubstringWithDrop/replaceWithDrop.kt.after deleted file mode 100644 index 5d3f38ae406..00000000000 --- a/idea/testData/intentions/replaceSubstringWithDrop/replaceWithDrop.kt.after +++ /dev/null @@ -1,5 +0,0 @@ -// WITH_RUNTIME - -fun foo(s: String) { - s.drop(4) -} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithDrop/semicolon.kt b/idea/testData/intentions/replaceSubstringWithDrop/semicolon.kt deleted file mode 100644 index 3b682cb4190..00000000000 --- a/idea/testData/intentions/replaceSubstringWithDrop/semicolon.kt +++ /dev/null @@ -1,5 +0,0 @@ -// WITH_RUNTIME - -fun foo(s: String) { - s.substring(4); -} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithDrop/semicolon.kt.after b/idea/testData/intentions/replaceSubstringWithDrop/semicolon.kt.after deleted file mode 100644 index 9a0b9134d6b..00000000000 --- a/idea/testData/intentions/replaceSubstringWithDrop/semicolon.kt.after +++ /dev/null @@ -1,5 +0,0 @@ -// WITH_RUNTIME - -fun foo(s: String) { - s.drop(4); -} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithDrop/substringWithTwoArguments.kt b/idea/testData/intentions/replaceSubstringWithDrop/substringWithTwoArguments.kt deleted file mode 100644 index f791c078b76..00000000000 --- a/idea/testData/intentions/replaceSubstringWithDrop/substringWithTwoArguments.kt +++ /dev/null @@ -1,6 +0,0 @@ -// IS_APPLICABLE: false -// WITH_RUNTIME - -fun foo(s: String) { - s.substring(0, 10) -} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithDropLast/methodCallReceiver.kt b/idea/testData/intentions/replaceSubstringWithDropLast/methodCallReceiver.kt new file mode 100644 index 00000000000..cf2ef46af17 --- /dev/null +++ b/idea/testData/intentions/replaceSubstringWithDropLast/methodCallReceiver.kt @@ -0,0 +1,10 @@ +// WITH_RUNTIME +// IS_APPLICABLE: false + +class A() { + fun bar(): String = null!! +} + +fun foo(a: A) { + a.bar().substring(0, a.bar().length - 5) +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithSubstringAfter/methodCallReceiver.kt b/idea/testData/intentions/replaceSubstringWithSubstringAfter/methodCallReceiver.kt new file mode 100644 index 00000000000..4885d5ee9d0 --- /dev/null +++ b/idea/testData/intentions/replaceSubstringWithSubstringAfter/methodCallReceiver.kt @@ -0,0 +1,10 @@ +// WITH_RUNTIME +// IS_APPLICABLE: false + +class A() { + fun bar(): String = null!! +} + +fun foo(a: A) { + a.bar().substring(a.bar().indexOf('x')) +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSubstringWithSubstringBefore/methodCallReceiver.kt b/idea/testData/intentions/replaceSubstringWithSubstringBefore/methodCallReceiver.kt new file mode 100644 index 00000000000..74f9dde72a4 --- /dev/null +++ b/idea/testData/intentions/replaceSubstringWithSubstringBefore/methodCallReceiver.kt @@ -0,0 +1,10 @@ +// WITH_RUNTIME +// IS_APPLICABLE: false + +class A() { + fun bar(): String = null!! +} + +fun foo(a: A) { + a.bar().substring(0, a.bar().indexOf('x')) +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index f3c9fed4608..56771b91627 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -7573,33 +7573,6 @@ public class IntentionTestGenerated extends AbstractIntentionTest { } } - @TestMetadata("idea/testData/intentions/replaceSubstringWithDrop") - @TestDataPath("$PROJECT_ROOT") - @RunWith(JUnit3RunnerWithInners.class) - public static class ReplaceSubstringWithDrop extends AbstractIntentionTest { - public void testAllFilesPresentInReplaceSubstringWithDrop() throws Exception { - KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/replaceSubstringWithDrop"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true); - } - - @TestMetadata("replaceWithDrop.kt") - public void testReplaceWithDrop() throws Exception { - String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSubstringWithDrop/replaceWithDrop.kt"); - doTest(fileName); - } - - @TestMetadata("semicolon.kt") - public void testSemicolon() throws Exception { - String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSubstringWithDrop/semicolon.kt"); - doTest(fileName); - } - - @TestMetadata("substringWithTwoArguments.kt") - public void testSubstringWithTwoArguments() throws Exception { - String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSubstringWithDrop/substringWithTwoArguments.kt"); - doTest(fileName); - } - } - @TestMetadata("idea/testData/intentions/replaceSubstringWithDropLast") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) @@ -7614,6 +7587,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest { doTest(fileName); } + @TestMetadata("methodCallReceiver.kt") + public void testMethodCallReceiver() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSubstringWithDropLast/methodCallReceiver.kt"); + doTest(fileName); + } + @TestMetadata("nonZeroFirstArgument.kt") public void testNonZeroFirstArgument() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSubstringWithDropLast/nonZeroFirstArgument.kt"); @@ -7647,6 +7626,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest { doTest(fileName); } + @TestMetadata("methodCallReceiver.kt") + public void testMethodCallReceiver() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSubstringWithSubstringAfter/methodCallReceiver.kt"); + doTest(fileName); + } + @TestMetadata("replaceWithSubstringAfter.kt") public void testReplaceWithSubstringAfter() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSubstringWithSubstringAfter/replaceWithSubstringAfter.kt"); @@ -7674,6 +7659,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest { doTest(fileName); } + @TestMetadata("methodCallReceiver.kt") + public void testMethodCallReceiver() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSubstringWithSubstringBefore/methodCallReceiver.kt"); + doTest(fileName); + } + @TestMetadata("nonZeroFirstArgument.kt") public void testNonZeroFirstArgument() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSubstringWithSubstringBefore/nonZeroFirstArgument.kt");