diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/CleanupDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/CleanupDetector.java index f43285acd35..ebf60e28e55 100644 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/CleanupDetector.java +++ b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/CleanupDetector.java @@ -365,15 +365,16 @@ public class CleanupDetector extends Detector implements UastScanner { // getFragmentManager().beginTransaction().addToBackStack("test") // .disallowAddToBackStack().hide(mFragment2).setBreadCrumbShortTitle("test") // .show(mFragment2).setCustomAnimations(0, 0).commit(); - UElement parent = node.getParent(); - while (parent instanceof UCallExpression) { - UCallExpression methodInvocation = (UCallExpression) parent; - if (isTransactionCommitMethodCall(context, methodInvocation) + List chains = UastUtils.getQualifiedChains(node); + if (chains.size() > 0) { + UExpression lastExpression = chains.get(chains.size() - 1); + if (lastExpression instanceof UCallExpression) { + UCallExpression methodInvocation = (UCallExpression) lastExpression; + if (isTransactionCommitMethodCall(context, methodInvocation) || isShowFragmentMethodCall(context, methodInvocation)) { - return true; + return true; + } } - - parent = parent.getParent(); } return false; @@ -413,7 +414,17 @@ public class CleanupDetector extends Detector implements UastScanner { @Nullable public static UVariable getVariable(@NonNull UastAndroidContext context, @NonNull UElement expression) { - UElement parent = expression.getParent(); + if (!(expression instanceof UExpression)) { + return null; + } + + UQualifiedExpression topMostQualified = UastUtils.findTopMostQualifiedExpression(((UExpression) expression)); + if (topMostQualified == null) { + return null; + } + + UElement parent = topMostQualified.getParent(); + if (parent instanceof UBinaryExpression) { UBinaryExpression binaryExpression = (UBinaryExpression) parent; if (binaryExpression.getOperator() == UastBinaryOperator.ASSIGN) { diff --git a/plugins/uast-common/src/org/jetbrains/uast/UastUtils.kt b/plugins/uast-common/src/org/jetbrains/uast/UastUtils.kt index ff9a7cbbbaf..a86c6187c99 100644 --- a/plugins/uast-common/src/org/jetbrains/uast/UastUtils.kt +++ b/plugins/uast-common/src/org/jetbrains/uast/UastUtils.kt @@ -245,4 +245,35 @@ fun UExpression.endsWithQualified(fqName: String): Boolean { if (passedIdentifier != identifiers[i]) return false } return true +} + +fun UExpression.findTopMostQualifiedExpression(): UQualifiedExpression? { + val parent = this.parent + return when (parent) { + is UQualifiedExpression -> parent.findTopMostQualifiedExpression() + else -> if (this is UQualifiedExpression) this else null + } +} + +fun UExpression.getQualifiedChains(): List { + fun collect(expr: UQualifiedExpression, chains: MutableList) { + val receiver = expr.receiver + if (receiver is UQualifiedExpression) { + collect(receiver, chains) + } else { + chains += receiver + } + + val selector = expr.selector + if (selector is UQualifiedExpression) { + collect(selector, chains) + } else { + chains += selector + } + } + + val qualifiedExpression = this.findTopMostQualifiedExpression() ?: return emptyList() + val chains = mutableListOf() + collect(qualifiedExpression, chains) + return chains } \ No newline at end of file diff --git a/plugins/uast-common/src/org/jetbrains/uast/declarations/UVariable.kt b/plugins/uast-common/src/org/jetbrains/uast/declarations/UVariable.kt index c361535ae8b..43ffd1430f4 100644 --- a/plugins/uast-common/src/org/jetbrains/uast/declarations/UVariable.kt +++ b/plugins/uast-common/src/org/jetbrains/uast/declarations/UVariable.kt @@ -15,6 +15,7 @@ */ package org.jetbrains.uast +import org.jetbrains.uast.kinds.UastVariableInitialierKind import org.jetbrains.uast.visitor.UastVisitor interface UVariable : UDeclaration, UModifierOwner, UVisibilityOwner, UAnnotated { @@ -23,6 +24,12 @@ interface UVariable : UDeclaration, UModifierOwner, UVisibilityOwner, UAnnotated */ val initializer: UExpression? + + /** + * Return the variable initializer kind (simple initializer, property delegation, etc.). + */ + val initializerKind: UastVariableInitialierKind + /** * Return the variable kind. */ @@ -75,6 +82,7 @@ interface UVariable : UDeclaration, UModifierOwner, UVisibilityOwner, UAnnotated object UVariableNotResolved : UVariable { override val initializer = null + override val initializerKind = UastVariableInitialierKind.NO_INITIALIZER override val kind = UastVariableKind(ERROR_NAME) override val type = UastErrorType override val nameElement = null diff --git a/plugins/uast-common/src/org/jetbrains/uast/kinds/UastVariableInitialierKind.kt b/plugins/uast-common/src/org/jetbrains/uast/kinds/UastVariableInitialierKind.kt new file mode 100644 index 00000000000..78a390b0e6e --- /dev/null +++ b/plugins/uast-common/src/org/jetbrains/uast/kinds/UastVariableInitialierKind.kt @@ -0,0 +1,33 @@ +/* + * 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.uast.kinds + +open class UastVariableInitialierKind(val name: String) { + class Simple(name: String) : UastVariableInitialierKind(name) + class Delegation(name: String) : UastVariableInitialierKind(name) + + companion object { + @JvmField + val SIMPLE = Simple("simple") + + @JvmField + val DELEGATION = Simple("delegation") + + @JvmField + val NO_INITIALIZER = UastVariableInitialierKind("no_initializer") + } +} \ No newline at end of file diff --git a/plugins/uast-common/src/org/jetbrains/uast/kinds/UastVariableKind.kt b/plugins/uast-common/src/org/jetbrains/uast/kinds/UastVariableKind.kt index fb997b903c5..c64da507a2c 100644 --- a/plugins/uast-common/src/org/jetbrains/uast/kinds/UastVariableKind.kt +++ b/plugins/uast-common/src/org/jetbrains/uast/kinds/UastVariableKind.kt @@ -19,15 +19,19 @@ package org.jetbrains.uast * Kinds of [UVariable]. */ open class UastVariableKind(val name: String) { + class Member(name: String) : UastVariableKind(name) + class LocalVariable(name: String) : UastVariableKind(name) + class ValueParameter(name: String) : UastVariableKind(name) + companion object { @JvmField - val LOCAL_VARIABLE = UastVariableKind("local") + val LOCAL_VARIABLE = LocalVariable("local") @JvmField - val MEMBER = UastVariableKind("member") + val MEMBER = Member("member") @JvmField - val VALUE_PARAMETER = UastVariableKind("parameter") + val VALUE_PARAMETER = ValueParameter("parameter") } override fun toString(): String{ diff --git a/plugins/uast-java/src/org/jetbrains/uast/java/declarations/JavaUClass.kt b/plugins/uast-java/src/org/jetbrains/uast/java/declarations/JavaUClass.kt index d88e0cb24e1..c29f6ec694e 100644 --- a/plugins/uast-java/src/org/jetbrains/uast/java/declarations/JavaUClass.kt +++ b/plugins/uast-java/src/org/jetbrains/uast/java/declarations/JavaUClass.kt @@ -21,6 +21,7 @@ import com.intellij.psi.util.ClassUtil import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.PsiTypesUtil import org.jetbrains.uast.* +import org.jetbrains.uast.kinds.UastVariableInitialierKind import org.jetbrains.uast.psi.PsiElementBacked class JavaUClass( @@ -150,7 +151,7 @@ class JavaUClass( private class JavaUAnonymousClassConstructor( override val psi: PsiAnonymousClass, - val newExpression: PsiNewExpression, + newExpression: PsiNewExpression, override val parent: UElement ) : JavaAbstractUElement(), UFunction, PsiElementBacked, NoAnnotations, NoModifiers { override val kind = UastFunctionKind.CONSTRUCTOR @@ -191,6 +192,9 @@ private class JavaUAnonymousClassConstructorParameter( ) : JavaAbstractUElement(), UVariable, NoAnnotations, NoModifiers { override val initializer by lz { JavaConverter.convert(psi.expressions[index], this) } + override val initializerKind: UastVariableInitialierKind + get() = UastVariableInitialierKind.SIMPLE + override val kind: UastVariableKind get() = UastVariableKind.VALUE_PARAMETER diff --git a/plugins/uast-java/src/org/jetbrains/uast/java/declarations/JavaUVariable.kt b/plugins/uast-java/src/org/jetbrains/uast/java/declarations/JavaUVariable.kt index e3b2035e8f5..d111b4aeb8e 100644 --- a/plugins/uast-java/src/org/jetbrains/uast/java/declarations/JavaUVariable.kt +++ b/plugins/uast-java/src/org/jetbrains/uast/java/declarations/JavaUVariable.kt @@ -19,6 +19,9 @@ import com.intellij.psi.PsiField import com.intellij.psi.PsiLocalVariable import com.intellij.psi.PsiVariable import org.jetbrains.uast.* +import org.jetbrains.uast.kinds.UastVariableInitialierKind +import org.jetbrains.uast.kinds.UastVariableInitialierKind.Companion.NO_INITIALIZER +import org.jetbrains.uast.kinds.UastVariableInitialierKind.Companion.SIMPLE import org.jetbrains.uast.psi.PsiElementBacked class JavaUVariable( @@ -33,6 +36,9 @@ class JavaUVariable( override val initializer by lz { JavaConverter.convertOrEmpty(psi.initializer, this) } + override val initializerKind: UastVariableInitialierKind + get() = if (psi.initializer != null) SIMPLE else NO_INITIALIZER + override val kind = when (psi) { is PsiField -> UastVariableKind.MEMBER is PsiLocalVariable -> UastVariableKind.LOCAL_VARIABLE diff --git a/plugins/uast-java/src/org/jetbrains/uast/java/declarations/JavaValueParameterUVariable.kt b/plugins/uast-java/src/org/jetbrains/uast/java/declarations/JavaValueParameterUVariable.kt index 873135f0e24..36b1a173c5e 100644 --- a/plugins/uast-java/src/org/jetbrains/uast/java/declarations/JavaValueParameterUVariable.kt +++ b/plugins/uast-java/src/org/jetbrains/uast/java/declarations/JavaValueParameterUVariable.kt @@ -17,6 +17,7 @@ package org.jetbrains.uast.java import com.intellij.psi.PsiParameter import org.jetbrains.uast.* +import org.jetbrains.uast.kinds.UastVariableInitialierKind import org.jetbrains.uast.psi.PsiElementBacked class JavaValueParameterUVariable( @@ -32,6 +33,9 @@ class JavaValueParameterUVariable( override val initializer: UExpression? get() = null + override val initializerKind: UastVariableInitialierKind + get() = UastVariableInitialierKind.NO_INITIALIZER + override val kind: UastVariableKind get() = UastVariableKind.VALUE_PARAMETER diff --git a/plugins/uast-kotlin/src/org/jetbrains/kotlin/uast/declarations/KotlinUVariable.kt b/plugins/uast-kotlin/src/org/jetbrains/kotlin/uast/declarations/KotlinUVariable.kt index 8f4c171e027..dae3dee0785 100644 --- a/plugins/uast-kotlin/src/org/jetbrains/kotlin/uast/declarations/KotlinUVariable.kt +++ b/plugins/uast-kotlin/src/org/jetbrains/kotlin/uast/declarations/KotlinUVariable.kt @@ -23,6 +23,7 @@ import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.uast.* +import org.jetbrains.uast.kinds.UastVariableInitialierKind import org.jetbrains.uast.psi.PsiElementBacked open class KotlinUVariable( @@ -34,7 +35,19 @@ open class KotlinUVariable( override val nameElement by lz { KotlinDumbUElement(psi.nameIdentifier, this) } - override val initializer by lz { KotlinConverter.convertOrEmpty(psi.initializer, this) } + override val initializer by lz { + val expression = (psi as? KtProperty)?.delegateExpression ?: psi.initializer + KotlinConverter.convertOrEmpty(expression, this) + } + + override val initializerKind by lz { + if ((psi as? KtProperty)?.delegateExpression != null) + UastVariableInitialierKind.DELEGATION + else if (psi.initializer != null) + UastVariableInitialierKind.SIMPLE + else + UastVariableInitialierKind.NO_INITIALIZER + } override val type by lz { val descriptor = psi.resolveToDescriptorIfAny() as? CallableDescriptor ?: return@lz UastErrorType @@ -131,7 +144,12 @@ class KotlinDestructuringUVariable( override val parent: UElement ) : KotlinAbstractUElement(), UVariable, PsiElementBacked { override val name = "var" + psi.text.hashCode() + override val initializer by lz { KotlinConverter.convertOrEmpty(psi.initializer, this) } + + override val initializerKind: UastVariableInitialierKind + get() = UastVariableInitialierKind.NO_INITIALIZER + override val kind = UastVariableKind.LOCAL_VARIABLE override val type: UType get() = initializer.getExpressionType() ?: UastErrorType @@ -155,6 +173,9 @@ class KotlinParameterUVariable( override val initializer by lz { KotlinConverter.convert(psi.defaultValue, this) as? UExpression } + override val initializerKind: UastVariableInitialierKind + get() = UastVariableInitialierKind.NO_INITIALIZER + override val kind = UastVariableKind.VALUE_PARAMETER override val type by lz { diff --git a/plugins/uast-kotlin/src/org/jetbrains/kotlin/uast/declarations/kotlinUFunctions.kt b/plugins/uast-kotlin/src/org/jetbrains/kotlin/uast/declarations/kotlinUFunctions.kt index d530917c1f7..622e58573d1 100644 --- a/plugins/uast-kotlin/src/org/jetbrains/kotlin/uast/declarations/kotlinUFunctions.kt +++ b/plugins/uast-kotlin/src/org/jetbrains/kotlin/uast/declarations/kotlinUFunctions.kt @@ -30,6 +30,7 @@ import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.uast.* +import org.jetbrains.uast.kinds.UastVariableInitialierKind import org.jetbrains.uast.psi.PsiElementBacked abstract class KotlinAbstractUFunction : KotlinAbstractUElement(), UFunction, PsiElementBacked { @@ -203,6 +204,8 @@ open class KotlinObjectLiteralConstructorUFunction( object : UVariable { override val initializer: UExpression? get() = null + override val initializerKind: UastVariableInitialierKind + get() = UastVariableInitialierKind.NO_INITIALIZER override val kind: UastVariableKind get() = UastVariableKind.VALUE_PARAMETER override val type: UType diff --git a/plugins/uast-kotlin/test/org/jetbrains/kotlin/uast/KotlinLintTestGenerated.java b/plugins/uast-kotlin/test/org/jetbrains/kotlin/uast/KotlinLintTestGenerated.java index b806b250fbc..c31feeb24d2 100644 --- a/plugins/uast-kotlin/test/org/jetbrains/kotlin/uast/KotlinLintTestGenerated.java +++ b/plugins/uast-kotlin/test/org/jetbrains/kotlin/uast/KotlinLintTestGenerated.java @@ -53,6 +53,12 @@ public class KotlinLintTestGenerated extends AbstractKotlinLintTest { doTest(fileName); } + @TestMetadata("commitFragment.kt") + public void testCommitFragment() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("plugins/uast-kotlin/testData/lint/commitFragment.kt"); + doTest(fileName); + } + @TestMetadata("javaPerformance.kt") public void testJavaPerformance() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("plugins/uast-kotlin/testData/lint/javaPerformance.kt"); diff --git a/plugins/uast-kotlin/testData/lint/commitFragment.kt b/plugins/uast-kotlin/testData/lint/commitFragment.kt new file mode 100644 index 00000000000..73b039241af --- /dev/null +++ b/plugins/uast-kotlin/testData/lint/commitFragment.kt @@ -0,0 +1,27 @@ +// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintCommitTransactionInspection + +import android.app.Activity +import android.app.FragmentTransaction +import android.os.Bundle + +class MainActivity : Activity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + //OK + val transaction = fragmentManager.beginTransaction() + val transaction2: FragmentTransaction + transaction2 = fragmentManager.beginTransaction() + transaction.commit() + transaction2.commit() + + //WARNING + @Suppress("UNUSED_VARIABLE") + val transaction3 = fragmentManager.beginTransaction() + + //OK + fragmentManager.beginTransaction().commit() + fragmentManager.beginTransaction().add(null, "A").commit() + } +}