diff --git a/idea/resources/inspectionDescriptions/NestedLambdaShadowedImplicitParameter.html b/idea/resources/inspectionDescriptions/NestedLambdaShadowedImplicitParameter.html new file mode 100644 index 00000000000..4e34dc0a0de --- /dev/null +++ b/idea/resources/inspectionDescriptions/NestedLambdaShadowedImplicitParameter.html @@ -0,0 +1,5 @@ + + +This inspection reports a nested lambda with shadowed implicit parameter. + + diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index ad7edcab688..430258c85a9 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -2882,6 +2882,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. language="kotlin" /> + + diff --git a/idea/src/META-INF/plugin.xml.172 b/idea/src/META-INF/plugin.xml.172 index 35af67d21bb..90933f92fac 100644 --- a/idea/src/META-INF/plugin.xml.172 +++ b/idea/src/META-INF/plugin.xml.172 @@ -2880,6 +2880,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. language="kotlin" /> + + diff --git a/idea/src/META-INF/plugin.xml.173 b/idea/src/META-INF/plugin.xml.173 index 0bda9b150ec..60fb2a0204e 100644 --- a/idea/src/META-INF/plugin.xml.173 +++ b/idea/src/META-INF/plugin.xml.173 @@ -2880,6 +2880,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. language="kotlin" /> + + diff --git a/idea/src/META-INF/plugin.xml.182 b/idea/src/META-INF/plugin.xml.182 index 533bc8680e7..4e360093eca 100644 --- a/idea/src/META-INF/plugin.xml.182 +++ b/idea/src/META-INF/plugin.xml.182 @@ -2881,6 +2881,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. language="kotlin" /> + + diff --git a/idea/src/META-INF/plugin.xml.as31 b/idea/src/META-INF/plugin.xml.as31 index 97fbbcdb3e3..3429c3dc8a6 100644 --- a/idea/src/META-INF/plugin.xml.as31 +++ b/idea/src/META-INF/plugin.xml.as31 @@ -2880,6 +2880,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. language="kotlin" /> + + diff --git a/idea/src/META-INF/plugin.xml.as32 b/idea/src/META-INF/plugin.xml.as32 index f69651737f0..174450e5e59 100644 --- a/idea/src/META-INF/plugin.xml.as32 +++ b/idea/src/META-INF/plugin.xml.as32 @@ -2880,6 +2880,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. language="kotlin" /> + + diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/NestedLambdaShadowedImplicitParameterInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/NestedLambdaShadowedImplicitParameterInspection.kt new file mode 100644 index 00000000000..885c1b8fedb --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/NestedLambdaShadowedImplicitParameterInspection.kt @@ -0,0 +1,70 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.inspections + +import com.intellij.codeInspection.LocalQuickFix +import com.intellij.codeInspection.ProblemDescriptor +import com.intellij.codeInspection.ProblemHighlightType +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.PsiElementVisitor +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.refactoring.rename.KotlinVariableInplaceRenameHandler +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.getOrCreateParameterList +import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypesAndPredicate +import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType +import org.jetbrains.kotlin.psi.psiUtil.startOffset +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode + +class NestedLambdaShadowedImplicitParameterInspection : AbstractKotlinInspection() { + + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { + return lambdaExpressionVisitor(fun(lambda: KtLambdaExpression) { + val context: BindingContext by lazy { lambda.analyze(BodyResolveMode.PARTIAL) } + if (lambda.valueParameters.isNotEmpty() || lambda.functionDescriptor(context)?.valueParameters?.size != 1) return + if (lambda.getParentImplicitParameterLambda(context) == null) return + val callee = lambda.getStrictParentOfType()?.calleeExpression ?: return + holder.registerProblem( + callee, + "Implicit parameter 'it' of enclosing lambda is shadowed", + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + AddExplicitParameterNameFix() + ) + }) + } + + private class AddExplicitParameterNameFix : LocalQuickFix { + override fun getName() = "Add explicit parameter name to outer lambda" + + override fun getFamilyName() = name + + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + val parentLambda = (descriptor.psiElement as? KtExpression)?.getParentImplicitParameterLambda() ?: return + val parameter = parentLambda.functionLiteral.getOrCreateParameterList().addParameterBefore( + KtPsiFactory(project).createLambdaParameterList("it").parameters.first(), null + ) + val editor = parentLambda.findExistingEditor() + if (editor != null) { + PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(editor.document) + editor.caretModel.moveToOffset(parameter.startOffset) + KotlinVariableInplaceRenameHandler().doRename(parameter, editor, null) + } + } + } +} + +private fun KtLambdaExpression.functionDescriptor(context: BindingContext) = context[BindingContext.FUNCTION, functionLiteral] + +private fun KtExpression.getParentImplicitParameterLambda( + context: BindingContext = this.analyze(BodyResolveMode.PARTIAL) +): KtLambdaExpression? { + return getParentOfTypesAndPredicate(true, KtLambdaExpression::class.java) { + it.valueParameters.isEmpty() && it.functionDescriptor(context)?.valueParameters?.size == 1 + } +} diff --git a/idea/testData/inspectionsLocal/nestedLambdaShadowedImplicitParameter/.inspection b/idea/testData/inspectionsLocal/nestedLambdaShadowedImplicitParameter/.inspection new file mode 100644 index 00000000000..def0c501150 --- /dev/null +++ b/idea/testData/inspectionsLocal/nestedLambdaShadowedImplicitParameter/.inspection @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.inspections.NestedLambdaShadowedImplicitParameterInspection \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/nestedLambdaShadowedImplicitParameter/explicit.kt b/idea/testData/inspectionsLocal/nestedLambdaShadowedImplicitParameter/explicit.kt new file mode 100644 index 00000000000..982611dcbb0 --- /dev/null +++ b/idea/testData/inspectionsLocal/nestedLambdaShadowedImplicitParameter/explicit.kt @@ -0,0 +1,10 @@ +// PROBLEM: none + +fun foo(f: (String) -> Unit) {} + +fun test() { + foo { + foo { s -> + } + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/nestedLambdaShadowedImplicitParameter/explicitParent.kt b/idea/testData/inspectionsLocal/nestedLambdaShadowedImplicitParameter/explicitParent.kt new file mode 100644 index 00000000000..61929df729b --- /dev/null +++ b/idea/testData/inspectionsLocal/nestedLambdaShadowedImplicitParameter/explicitParent.kt @@ -0,0 +1,10 @@ +// PROBLEM: none + +fun foo(f: (String) -> Unit) {} + +fun test() { + foo { s -> + foo { + } + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/nestedLambdaShadowedImplicitParameter/implicit.kt b/idea/testData/inspectionsLocal/nestedLambdaShadowedImplicitParameter/implicit.kt new file mode 100644 index 00000000000..95c4b2481c2 --- /dev/null +++ b/idea/testData/inspectionsLocal/nestedLambdaShadowedImplicitParameter/implicit.kt @@ -0,0 +1,8 @@ +fun foo(f: (String) -> Unit) {} + +fun test() { + foo { + foo { + } + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/nestedLambdaShadowedImplicitParameter/implicit.kt.after b/idea/testData/inspectionsLocal/nestedLambdaShadowedImplicitParameter/implicit.kt.after new file mode 100644 index 00000000000..d24a6946976 --- /dev/null +++ b/idea/testData/inspectionsLocal/nestedLambdaShadowedImplicitParameter/implicit.kt.after @@ -0,0 +1,8 @@ +fun foo(f: (String) -> Unit) {} + +fun test() { + foo { it -> + foo { + } + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/nestedLambdaShadowedImplicitParameter/implicit2.kt b/idea/testData/inspectionsLocal/nestedLambdaShadowedImplicitParameter/implicit2.kt new file mode 100644 index 00000000000..ab341eb653e --- /dev/null +++ b/idea/testData/inspectionsLocal/nestedLambdaShadowedImplicitParameter/implicit2.kt @@ -0,0 +1,11 @@ +fun foo(f: (String) -> Unit) {} +fun bar(f: (Int) -> Unit) {} + +fun test() { + foo { + val s: String = it + bar { + val i: Int = it + } + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/nestedLambdaShadowedImplicitParameter/implicit2.kt.after b/idea/testData/inspectionsLocal/nestedLambdaShadowedImplicitParameter/implicit2.kt.after new file mode 100644 index 00000000000..e1ddac140f1 --- /dev/null +++ b/idea/testData/inspectionsLocal/nestedLambdaShadowedImplicitParameter/implicit2.kt.after @@ -0,0 +1,11 @@ +fun foo(f: (String) -> Unit) {} +fun bar(f: (Int) -> Unit) {} + +fun test() { + foo { it -> + val s: String = it + bar { + val i: Int = it + } + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/nestedLambdaShadowedImplicitParameter/implicitGrandParent.kt b/idea/testData/inspectionsLocal/nestedLambdaShadowedImplicitParameter/implicitGrandParent.kt new file mode 100644 index 00000000000..d984282f16e --- /dev/null +++ b/idea/testData/inspectionsLocal/nestedLambdaShadowedImplicitParameter/implicitGrandParent.kt @@ -0,0 +1,10 @@ +fun foo(f: (String) -> Unit) {} + +fun test() { + foo { + foo { s -> + foo { + } + } + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/nestedLambdaShadowedImplicitParameter/implicitGrandParent.kt.after b/idea/testData/inspectionsLocal/nestedLambdaShadowedImplicitParameter/implicitGrandParent.kt.after new file mode 100644 index 00000000000..a4cb5a10b71 --- /dev/null +++ b/idea/testData/inspectionsLocal/nestedLambdaShadowedImplicitParameter/implicitGrandParent.kt.after @@ -0,0 +1,10 @@ +fun foo(f: (String) -> Unit) {} + +fun test() { + foo { it -> + foo { s -> + foo { + } + } + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/nestedLambdaShadowedImplicitParameter/receiver.kt b/idea/testData/inspectionsLocal/nestedLambdaShadowedImplicitParameter/receiver.kt new file mode 100644 index 00000000000..867d84cea0f --- /dev/null +++ b/idea/testData/inspectionsLocal/nestedLambdaShadowedImplicitParameter/receiver.kt @@ -0,0 +1,11 @@ +// PROBLEM: none + +fun foo(f: (String) -> Unit) {} +fun bar(f: String.() -> Unit) {} + +fun test() { + foo { + bar { + } + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/nestedLambdaShadowedImplicitParameter/receiverParent.kt b/idea/testData/inspectionsLocal/nestedLambdaShadowedImplicitParameter/receiverParent.kt new file mode 100644 index 00000000000..b57560c9b1c --- /dev/null +++ b/idea/testData/inspectionsLocal/nestedLambdaShadowedImplicitParameter/receiverParent.kt @@ -0,0 +1,11 @@ +// PROBLEM: none + +fun foo(f: (String) -> Unit) {} +fun bar(f: String.() -> Unit) {} + +fun test() { + bar { + foo { + } + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java index 90b00ea0e9e..b5fd01b1f66 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -2814,6 +2814,54 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest { } } + @TestMetadata("idea/testData/inspectionsLocal/nestedLambdaShadowedImplicitParameter") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class NestedLambdaShadowedImplicitParameter extends AbstractLocalInspectionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath); + } + + public void testAllFilesPresentInNestedLambdaShadowedImplicitParameter() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/nestedLambdaShadowedImplicitParameter"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true); + } + + @TestMetadata("explicit.kt") + public void testExplicit() throws Exception { + runTest("idea/testData/inspectionsLocal/nestedLambdaShadowedImplicitParameter/explicit.kt"); + } + + @TestMetadata("explicitParent.kt") + public void testExplicitParent() throws Exception { + runTest("idea/testData/inspectionsLocal/nestedLambdaShadowedImplicitParameter/explicitParent.kt"); + } + + @TestMetadata("implicit.kt") + public void testImplicit() throws Exception { + runTest("idea/testData/inspectionsLocal/nestedLambdaShadowedImplicitParameter/implicit.kt"); + } + + @TestMetadata("implicit2.kt") + public void testImplicit2() throws Exception { + runTest("idea/testData/inspectionsLocal/nestedLambdaShadowedImplicitParameter/implicit2.kt"); + } + + @TestMetadata("implicitGrandParent.kt") + public void testImplicitGrandParent() throws Exception { + runTest("idea/testData/inspectionsLocal/nestedLambdaShadowedImplicitParameter/implicitGrandParent.kt"); + } + + @TestMetadata("receiver.kt") + public void testReceiver() throws Exception { + runTest("idea/testData/inspectionsLocal/nestedLambdaShadowedImplicitParameter/receiver.kt"); + } + + @TestMetadata("receiverParent.kt") + public void testReceiverParent() throws Exception { + runTest("idea/testData/inspectionsLocal/nestedLambdaShadowedImplicitParameter/receiverParent.kt"); + } + } + @TestMetadata("idea/testData/inspectionsLocal/nullChecksToSafeCall") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)