Merge branch 'rr/valentin/for-loop-indices'

This commit is contained in:
Valentin Kipyatkov
2015-07-22 09:48:18 +03:00
44 changed files with 536 additions and 2 deletions
@@ -23,6 +23,7 @@ import org.jetbrains.kotlin.descriptors.ClassKind.ENUM_ENTRY
import org.jetbrains.kotlin.descriptors.ClassKind.OBJECT
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.FqNameUnsafe
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.types.JetType
@@ -36,6 +37,12 @@ public fun DeclarationDescriptor.getImportableDescriptor(): DeclarationDescripto
}
}
public val DeclarationDescriptor.fqNameUnsafe: FqNameUnsafe
get() = DescriptorUtils.getFqName(this)
public val DeclarationDescriptor.fqNameSafe: FqName
get() = DescriptorUtils.getFqNameSafe(this)
public val DeclarationDescriptor.isExtension: Boolean
get() = this is CallableDescriptor && getExtensionReceiverParameter() != null
@@ -0,0 +1,5 @@
<html>
<body>
This inspection reports for loops iterating over a collection of values using "withIndex()" function with index variable not used in the loop body.
</body>
</html>
@@ -0,0 +1,3 @@
for (<spot>(i, x) in foo.withIndices()</spot>) {
}
@@ -0,0 +1,3 @@
for (<spot>x in foo</spot>) {
}
@@ -0,0 +1,5 @@
<html>
<body>
This intention adds an index variable to a for loop iterating over a collection.
</body>
</html>
@@ -0,0 +1,3 @@
for (<spot>x in foo</spot>) {
}
@@ -0,0 +1,3 @@
for (<spot>(i, x) in foo.withIndices()</spot>) {
}
@@ -0,0 +1,5 @@
<html>
<body>
This intention removes the index variable from a for loop iterating over a collection.
</body>
</html>
+17
View File
@@ -794,6 +794,16 @@
<category>Kotlin</category>
</intentionAction>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.AddForLoopIndicesIntention</className>
<category>Kotlin</category>
</intentionAction>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.RemoveForLoopIndicesIntention</className>
<category>Kotlin</category>
</intentionAction>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.SwapBinaryExpressionIntention</className>
<category>Kotlin</category>
@@ -1071,6 +1081,13 @@
cleanupTool="true"
level="WARNING"/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.RemoveForLoopIndicesInspection"
displayName="Index is unused"
groupName="Kotlin"
enabledByDefault="true"
level="WARNING"
/>
<project.converterProvider implementation="org.jetbrains.kotlin.idea.converters.JetRunConfigurationSettingsFormatConverterProvider"/>
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
@@ -0,0 +1,103 @@
/*
* Copyright 2010-2014 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.LowPriorityAction
import com.intellij.codeInsight.template.TemplateBuilderImpl
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiDocumentManager
import org.jetbrains.kotlin.analyzer.analyzeInContext
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.core.replaced
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.endOffset
import org.jetbrains.kotlin.psi.psiUtil.startOffset
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
public class AddForLoopIndicesIntention : JetSelfTargetingRangeIntention<JetForExpression>(javaClass(), "Add indices to 'for' loop"), LowPriorityAction {
private val WITH_INDEX_FQ_NAME = "kotlin.withIndex"
override fun applicabilityRange(element: JetForExpression): TextRange? {
if (element.loopParameter == null) return null
val loopRange = element.loopRange ?: return null
val bindingContext = element.analyze(BodyResolveMode.PARTIAL)
val resolvedCall = loopRange.getResolvedCall(bindingContext)
if (resolvedCall?.resultingDescriptor?.fqNameUnsafe?.asString() == WITH_INDEX_FQ_NAME) return null // already withIndex() call
val resolutionScope = bindingContext[BindingContext.RESOLUTION_SCOPE, element] ?: return null
val potentialExpression = createWithIndexExpression(loopRange)
val newBindingContext = potentialExpression.analyzeInContext(resolutionScope)
val newResolvedCall = potentialExpression.getResolvedCall(newBindingContext) ?: return null
if (newResolvedCall.resultingDescriptor.fqNameUnsafe.asString() != WITH_INDEX_FQ_NAME) return null
return TextRange(element.startOffset, element.body?.startOffset ?: element.endOffset)
}
override fun applyTo(element: JetForExpression, editor: Editor) {
val loopRange = element.loopRange!!
val loopParameter = element.loopParameter!!
val psiFactory = JetPsiFactory(element)
loopRange.replace(createWithIndexExpression(loopRange))
var multiParameter = (psiFactory.createExpressionByPattern("for((index, $0) in x){}", loopParameter.text) as JetForExpression).multiParameter!!
multiParameter = loopParameter.replaced(multiParameter)
val indexVariable = multiParameter.entries[0]
editor.caretModel.moveToOffset(indexVariable.startOffset)
runTemplate(editor, element, indexVariable)
}
private fun runTemplate(editor: Editor, forExpression: JetForExpression, indexVariable: JetMultiDeclarationEntry) {
PsiDocumentManager.getInstance(forExpression.project).doPostponedOperationsAndUnblockDocument(editor.document)
val templateBuilder = TemplateBuilderImpl(forExpression)
templateBuilder.replaceElement(indexVariable, ChooseStringExpression(listOf("index", "i")))
val body = forExpression.body
when (body) {
is JetBlockExpression -> {
val statement = body.statements.firstOrNull()
if (statement != null) {
templateBuilder.setEndVariableBefore(statement)
}
else {
templateBuilder.setEndVariableAfter(body.lBrace)
}
}
null -> forExpression.rightParenthesis.let { templateBuilder.setEndVariableAfter(it) }
else -> templateBuilder.setEndVariableBefore(body)
}
templateBuilder.run(editor, true)
}
private fun createWithIndexExpression(originalExpression: JetExpression): JetExpression {
return JetPsiFactory(originalExpression).createExpressionByPattern("$0.withIndex()", originalExpression)
}
}
@@ -24,7 +24,7 @@ import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
public class ConvertForEachToForLoopIntention : JetSelfTargetingOffsetIndependentIntention<JetSimpleNameExpression>(javaClass(), "Replace with a for loop") {
public class ConvertForEachToForLoopIntention : JetSelfTargetingOffsetIndependentIntention<JetSimpleNameExpression>(javaClass(), "Replace with a 'for' loop") {
override fun isApplicableTo(element: JetSimpleNameExpression): Boolean {
if (element.getReferencedName() != "forEach") return false
@@ -25,7 +25,7 @@ import org.jetbrains.kotlin.psi.createExpressionByPattern
import org.jetbrains.kotlin.psi.psiUtil.contentRange
import org.jetbrains.kotlin.psi.psiUtil.endOffset
public class ConvertToForEachFunctionCallIntention : JetSelfTargetingIntention<JetForExpression>(javaClass(), "Replace with a forEach function call") {
public class ConvertToForEachFunctionCallIntention : JetSelfTargetingIntention<JetForExpression>(javaClass(), "Replace with a 'forEach' function call") {
override fun isApplicableTo(element: JetForExpression, caretOffset: Int): Boolean {
val rParen = element.getRightParenthesis() ?: return false
if (caretOffset > rParen.endOffset) return false // available only on the loop header, not in the body
@@ -0,0 +1,72 @@
/*
* Copyright 2010-2014 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.codeInspection.ProblemHighlightType
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.util.TextRange
import com.intellij.psi.search.searches.ReferencesSearch
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.editor.fixers.range
import org.jetbrains.kotlin.idea.inspections.IntentionBasedInspection
import org.jetbrains.kotlin.psi.JetDotQualifiedExpression
import org.jetbrains.kotlin.psi.JetForExpression
import org.jetbrains.kotlin.psi.JetPsiFactory
import org.jetbrains.kotlin.psi.createExpressionByPattern
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
public class RemoveForLoopIndicesInspection : IntentionBasedInspection<JetForExpression>(
listOf(IntentionBasedInspection.IntentionData(RemoveForLoopIndicesIntention())),
"Index is not used in the loop body",
javaClass()
) {
override val problemHighlightType: ProblemHighlightType
get() = ProblemHighlightType.LIKE_UNUSED_SYMBOL
}
public class RemoveForLoopIndicesIntention : JetSelfTargetingRangeIntention<JetForExpression>(javaClass(), "Remove indices in 'for' loop") {
private val WITH_INDEX_FQ_NAME = "kotlin.withIndex"
override fun applicabilityRange(element: JetForExpression): TextRange? {
val loopRange = element.loopRange as? JetDotQualifiedExpression ?: return null
val multiParameter = element.multiParameter ?: return null
if (multiParameter.entries.size() != 2) return null
val bindingContext = element.analyze(BodyResolveMode.PARTIAL)
val resolvedCall = loopRange.getResolvedCall(bindingContext)
if (resolvedCall?.resultingDescriptor?.fqNameUnsafe?.asString() != WITH_INDEX_FQ_NAME) return null
val indexVar = multiParameter.entries[0]
if (ReferencesSearch.search(indexVar).any()) return null
return indexVar.nameIdentifier?.range
}
override fun applyTo(element: JetForExpression, editor: Editor) {
val multiParameter = element.multiParameter!!
val loopRange = element.loopRange as JetDotQualifiedExpression
val elementVar = multiParameter.entries[1]
val loop = JetPsiFactory(element).createExpressionByPattern("for ($0 in _) {}", elementVar.text) as JetForExpression
multiParameter.replace(loop.loopParameter!!)
loopRange.replace(loopRange.receiverExpression)
}
}
+1
View File
@@ -0,0 +1 @@
org.jetbrains.kotlin.idea.intentions.AddForLoopIndicesIntention
@@ -0,0 +1,7 @@
// WITH_RUNTIME
fun a() {
val b = listOf(1,2,3,4,5)
for (<caret>c : Int in b) {
}
}
@@ -0,0 +1,7 @@
// WITH_RUNTIME
fun a() {
val b = listOf(1,2,3,4,5)
for ((index, c : Int) in b.withIndex()) {<caret>
}
}
@@ -0,0 +1,7 @@
// IS_APPLICABLE: false
// WITH_RUNTIME
fun b(c: List<String>) {
for ((<caret>indexVariable, d) in c.withIndex()) {
}
}
@@ -0,0 +1,7 @@
// IS_APPLICABLE: false
// WITH_RUNTIME
fun foo() {
for (a in listOf(1, 2, 3)) {<caret>
}
}
@@ -0,0 +1,7 @@
// IS_APPLICABLE: FALSE
// WITH_RUNTIME
fun foo(bar: Map<String, String>) {
for (a <caret>in bar) {
}
}
@@ -0,0 +1,9 @@
// WITH_RUNTIME
// IS_APPLICABLE: FALSE
fun String.withIndex(): Int = 42
fun foo(s: String) {
for (<caret>a in s) {
}
}
@@ -0,0 +1,7 @@
// IS_APPLICABLE: false
// ERROR: Unresolved reference: b
fun foo() {
for (a <caret>in b) {
}
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
fun foo(bar: IntArray) {
for (<caret>a in bar) {
}
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
fun foo(bar: IntArray) {
for ((index, a) in bar.withIndex()) {<caret>
}
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
fun foo(bar: Iterable<Int>) {
for (<caret>a in bar) {
}
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
fun foo(bar: Iterable<Int>) {
for ((index, a) in bar.withIndex()) {<caret>
}
}
+4
View File
@@ -0,0 +1,4 @@
// WITH_RUNTIME
fun foo(bar: IntArray) {
for (<caret>a in bar)
}
@@ -0,0 +1,4 @@
// WITH_RUNTIME
fun foo(bar: IntArray) {
for ((index, a) in bar.withIndex())<caret>
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
fun foo(bar: Array<Object>) {
for (<caret>a in bar) {
}
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
fun foo(bar: Array<Object>) {
for ((index, a) in bar.withIndex()) {<caret>
}
}
@@ -0,0 +1,5 @@
// WITH_RUNTIME
fun foo(bar: Sequence<String>) {
for (<caret>a in bar)
print(a)
}
@@ -0,0 +1,5 @@
// WITH_RUNTIME
fun foo(bar: Sequence<String>) {
for ((index, a) in bar.withIndex())
<caret>print(a)
}
@@ -0,0 +1,7 @@
// WITH_RUNTIME
fun a() {
val b = listOf(1,2,3,4,5)
for (<caret>c in b) {
}
}
@@ -0,0 +1,7 @@
// WITH_RUNTIME
fun a() {
val b = listOf(1,2,3,4,5)
for ((index, c) in b.withIndex()) {<caret>
}
}
+6
View File
@@ -0,0 +1,6 @@
// WITH_RUNTIME
fun foo(bar: String) {
for (<caret>a in bar) {
print(a)
}
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
fun foo(bar: String) {
for ((index, a) in bar.withIndex()) {
<caret>print(a)
}
}
@@ -0,0 +1 @@
org.jetbrains.kotlin.idea.intentions.RemoveForLoopIndicesIntention
@@ -0,0 +1,6 @@
// IS_APPLICABLE: false
fun foo(bar: List<String>) {
for (<caret>a in bar) {
}
}
@@ -0,0 +1,9 @@
// IS_APPLICABLE: FALSE
// WITH_RUNTIME
fun foo(b: List<Int>) : Int {
for ((<caret>i, c) in b.withIndex()) {
return i
}
return 0
}
@@ -0,0 +1,10 @@
// WITH_RUNTIME
// IS_APPLICABLE: FALSE
fun Int.withIndex(): List<Pair<Int, Int>> = linkedListOf<Pair<Int, Int>>()
fun foo(s: Int) {
for ((index<caret>, a) in s.withIndex()) {
}
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
fun foo(bar: List<Int>) {
for ((i<caret> : Int, b: Int) in bar.withIndex()) {
}
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
fun foo(bar: List<Int>) {
for (b: Int in bar) {
}
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
fun foo(bar: List<String>) {
for ((i<caret>,a) in bar.withIndex()) {
}
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
fun foo(bar: List<String>) {
for (a in bar) {
}
}
@@ -92,6 +92,93 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
}
}
@TestMetadata("idea/testData/intentions/addForLoopIndices")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class AddForLoopIndices extends AbstractIntentionTest {
public void testAllFilesPresentInAddForLoopIndices() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/addForLoopIndices"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true);
}
@TestMetadata("explicitParamType.kt")
public void testExplicitParamType() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/addForLoopIndices/explicitParamType.kt");
doTest(fileName);
}
@TestMetadata("inapplicableExistingIndices.kt")
public void testInapplicableExistingIndices() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/addForLoopIndices/inapplicableExistingIndices.kt");
doTest(fileName);
}
@TestMetadata("inapplicableInBody.kt")
public void testInapplicableInBody() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/addForLoopIndices/inapplicableInBody.kt");
doTest(fileName);
}
@TestMetadata("inapplicableOnMap.kt")
public void testInapplicableOnMap() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/addForLoopIndices/inapplicableOnMap.kt");
doTest(fileName);
}
@TestMetadata("inapplicableOverridenFunction.kt")
public void testInapplicableOverridenFunction() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/addForLoopIndices/inapplicableOverridenFunction.kt");
doTest(fileName);
}
@TestMetadata("inapplicableUnresolved.kt")
public void testInapplicableUnresolved() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/addForLoopIndices/inapplicableUnresolved.kt");
doTest(fileName);
}
@TestMetadata("intArray.kt")
public void testIntArray() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/addForLoopIndices/intArray.kt");
doTest(fileName);
}
@TestMetadata("iterable.kt")
public void testIterable() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/addForLoopIndices/iterable.kt");
doTest(fileName);
}
@TestMetadata("noBody.kt")
public void testNoBody() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/addForLoopIndices/noBody.kt");
doTest(fileName);
}
@TestMetadata("objectArray.kt")
public void testObjectArray() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/addForLoopIndices/objectArray.kt");
doTest(fileName);
}
@TestMetadata("sequence.kt")
public void testSequence() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/addForLoopIndices/sequence.kt");
doTest(fileName);
}
@TestMetadata("simpleIntList.kt")
public void testSimpleIntList() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/addForLoopIndices/simpleIntList.kt");
doTest(fileName);
}
@TestMetadata("string.kt")
public void testString() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/addForLoopIndices/string.kt");
doTest(fileName);
}
}
@TestMetadata("idea/testData/intentions/addNameToArgument")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -6089,6 +6176,45 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
}
@TestMetadata("idea/testData/intentions/removeForLoopIndices")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class RemoveForLoopIndices extends AbstractIntentionTest {
public void testAllFilesPresentInRemoveForLoopIndices() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/removeForLoopIndices"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true);
}
@TestMetadata("inapplicableForLoop.kt")
public void testInapplicableForLoop() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/removeForLoopIndices/inapplicableForLoop.kt");
doTest(fileName);
}
@TestMetadata("inapplicableIndexUse.kt")
public void testInapplicableIndexUse() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/removeForLoopIndices/inapplicableIndexUse.kt");
doTest(fileName);
}
@TestMetadata("inapplicableOverridenFunction.kt")
public void testInapplicableOverridenFunction() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/removeForLoopIndices/inapplicableOverridenFunction.kt");
doTest(fileName);
}
@TestMetadata("loopWithType.kt")
public void testLoopWithType() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/removeForLoopIndices/loopWithType.kt");
doTest(fileName);
}
@TestMetadata("simpleLoopWithIndices.kt")
public void testSimpleLoopWithIndices() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/removeForLoopIndices/simpleLoopWithIndices.kt");
doTest(fileName);
}
}
@TestMetadata("idea/testData/intentions/removeUnnecessaryParentheses")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)