code review; add 'operator' also to next() and hasNext(); check toplevel functions

This commit is contained in:
Dmitry Jemerov
2015-09-24 20:35:21 +02:00
parent e0f8d68a5f
commit 8b0ccce4f1
5 changed files with 49 additions and 9 deletions
@@ -21,13 +21,15 @@ import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElementVisitor import com.intellij.psi.PsiElementVisitor
import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.lexer.JetModifierKeywordToken
import org.jetbrains.kotlin.lexer.JetTokens import org.jetbrains.kotlin.lexer.JetTokens
import org.jetbrains.kotlin.psi.JetClassOrObject
import org.jetbrains.kotlin.psi.JetModifierListOwner import org.jetbrains.kotlin.psi.JetModifierListOwner
import org.jetbrains.kotlin.psi.JetNamedFunction import org.jetbrains.kotlin.psi.JetNamedFunction
import org.jetbrains.kotlin.psi.JetVisitorVoid import org.jetbrains.kotlin.psi.JetVisitorVoid
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.resolve.dataClassUtils.isComponentLike import org.jetbrains.kotlin.resolve.dataClassUtils.isComponentLike
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.expressions.OperatorConventions import org.jetbrains.kotlin.types.expressions.OperatorConventions
public class OperatorModifierInspection : AbstractKotlinInspection(), CleanupLocalInspectionTool { public class OperatorModifierInspection : AbstractKotlinInspection(), CleanupLocalInspectionTool {
@@ -35,9 +37,13 @@ public class OperatorModifierInspection : AbstractKotlinInspection(), CleanupLoc
return object : JetVisitorVoid() { return object : JetVisitorVoid() {
override fun visitNamedFunction(function: JetNamedFunction) { override fun visitNamedFunction(function: JetNamedFunction) {
val nameIdentifier = function.nameIdentifier val nameIdentifier = function.nameIdentifier
if (nameIdentifier != null && function.isOperator() && !function.hasModifier(JetTokens.OPERATOR_KEYWORD)) { if (nameIdentifier != null &&
function.isMemberOrExtension() &&
function.isOperator() &&
!function.hasModifier(JetTokens.OPERATOR_KEYWORD)) {
holder.registerProblem(nameIdentifier, "Function defines an operator but isn't annotated as such", holder.registerProblem(nameIdentifier, "Function defines an operator but isn't annotated as such",
AddModifierLocalQuickFix(JetTokens.OPERATOR_KEYWORD)) AddModifierLocalQuickFix())
} }
} }
} }
@@ -49,7 +55,9 @@ public class OperatorModifierInspection : AbstractKotlinInspection(), CleanupLoc
if (arity == 0 && if (arity == 0 &&
(name in OperatorConventions.UNARY_OPERATION_NAMES.values() || (name in OperatorConventions.UNARY_OPERATION_NAMES.values() ||
name == OperatorConventions.ITERATOR || name == OperatorConventions.ITERATOR ||
isComponentLike(name))) { isComponentLike(name) ||
name == OperatorConventions.NEXT ||
(name == OperatorConventions.HAS_NEXT && isBooleanReturnType()))) {
return true return true
} }
if (arity == 1 && (name in OperatorConventions.BINARY_OPERATION_NAMES.values() || if (arity == 1 && (name in OperatorConventions.BINARY_OPERATION_NAMES.values() ||
@@ -70,21 +78,24 @@ public class OperatorModifierInspection : AbstractKotlinInspection(), CleanupLoc
return false return false
} }
private fun JetNamedFunction.isMemberOrExtension(): Boolean =
receiverTypeReference != null || getStrictParentOfType<JetClassOrObject>() != null
private fun JetNamedFunction.isIntReturnType(): Boolean { private fun JetNamedFunction.isIntReturnType(): Boolean {
return KotlinBuiltIns.isInt(analyze().getType(this) ?: return false) return KotlinBuiltIns.isInt(analyze(BodyResolveMode.PARTIAL).getType(this) ?: return false)
} }
private fun JetNamedFunction.isBooleanReturnType(): Boolean { private fun JetNamedFunction.isBooleanReturnType(): Boolean {
return KotlinBuiltIns.isBoolean(analyze().getType(this) ?: return false) return KotlinBuiltIns.isBoolean(analyze(BodyResolveMode.PARTIAL).getType(this) ?: return false)
} }
} }
class AddModifierLocalQuickFix(val modifier: JetModifierKeywordToken) : LocalQuickFix { private class AddModifierLocalQuickFix() : LocalQuickFix {
override fun getName(): String = "Add '${modifier.value}' modifier" override fun getName(): String = "Add 'operator' modifier"
override fun getFamilyName(): String = getName() override fun getFamilyName(): String = getName()
override fun applyFix(project: Project, descriptor: ProblemDescriptor) { override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val modifierListOwner = descriptor.psiElement.getNonStrictParentOfType<JetModifierListOwner>() val modifierListOwner = descriptor.psiElement.getNonStrictParentOfType<JetModifierListOwner>()
modifierListOwner?.addModifier(modifier) modifierListOwner?.addModifier(JetTokens.OPERATOR_KEYWORD)
} }
} }
@@ -0,0 +1,5 @@
// "Add 'operator' modifier" "true"
class A {
}
fun A.<caret>plus(other: A): A = A()
@@ -0,0 +1,5 @@
// "Add 'operator' modifier" "true"
class A {
}
operator fun A.<caret>plus(other: A): A = A()
@@ -0,0 +1,7 @@
// "Add 'operator' modifier" "false"
// ACTION: Convert to block body
// ACTION: Remove explicit type specification
class A {
}
fun <caret>plus(other: A): A = A()
@@ -4201,11 +4201,23 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
doTest(fileName); doTest(fileName);
} }
@TestMetadata("extension.kt")
public void testExtension() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/migration/operatorModifier/extension.kt");
doTest(fileName);
}
@TestMetadata("simple.kt") @TestMetadata("simple.kt")
public void testSimple() throws Exception { public void testSimple() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/migration/operatorModifier/simple.kt"); String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/migration/operatorModifier/simple.kt");
doTest(fileName); doTest(fileName);
} }
@TestMetadata("toplevel.kt")
public void testToplevel() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/migration/operatorModifier/toplevel.kt");
doTest(fileName);
}
} }
@TestMetadata("idea/testData/quickfix/migration/removeNameFromFunctionExpression") @TestMetadata("idea/testData/quickfix/migration/removeNameFromFunctionExpression")