diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtPsiFactory.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtPsiFactory.kt index 37b3cbab23f..99207c3d726 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtPsiFactory.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtPsiFactory.kt @@ -1,17 +1,6 @@ /* - * Copyright 2010-2017 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. + * Copyright 2000-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.psi @@ -85,6 +74,9 @@ class KtPsiFactory @JvmOverloads constructor(private val project: Project, val m fun createThisExpression() = (createExpression("this.x") as KtQualifiedExpression).receiverExpression as KtThisExpression + fun createThisExpression(qualifier: String) = + (createExpression("this@$qualifier.x") as KtQualifiedExpression).receiverExpression as KtThisExpression + fun createCallArguments(text: String): KtValueArgumentList { val property = createProperty("val x = foo $text") return (property.initializer as KtCallExpression).valueArgumentList!! diff --git a/idea/resources/inspectionDescriptions/ScopeFunctionConversion.html b/idea/resources/inspectionDescriptions/ScopeFunctionConversion.html new file mode 100644 index 00000000000..b4bbf752b87 --- /dev/null +++ b/idea/resources/inspectionDescriptions/ScopeFunctionConversion.html @@ -0,0 +1,5 @@ + + +Provides actions for converting scope functions ('let', 'run', 'apply', 'also') between each other. + + \ No newline at end of file diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 39429f72f16..77da28a49f4 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -2644,6 +2644,15 @@ language="kotlin" /> + + diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/ScopeFunctionConversionInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/ScopeFunctionConversionInspection.kt new file mode 100644 index 00000000000..4d5cae50a85 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/ScopeFunctionConversionInspection.kt @@ -0,0 +1,364 @@ +/* + * Copyright 2000-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.* +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiElementVisitor +import com.intellij.psi.SmartPsiElementPointer +import com.intellij.refactoring.rename.inplace.VariableInplaceRenameHandler +import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor +import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.core.KotlinNameSuggester +import org.jetbrains.kotlin.idea.core.ShortenReferences +import org.jetbrains.kotlin.idea.refactoring.getThisLabelName +import org.jetbrains.kotlin.idea.references.resolveMainReferenceToDescriptors +import org.jetbrains.kotlin.idea.util.getReceiverTargetDescriptor +import org.jetbrains.kotlin.idea.util.getResolutionScope +import org.jetbrains.kotlin.incremental.components.NoLookupLocation +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.createSmartPointer +import org.jetbrains.kotlin.psi.psiUtil.endOffset +import org.jetbrains.kotlin.psi.psiUtil.getOrCreateParameterList +import org.jetbrains.kotlin.psi.psiUtil.startOffset +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.BindingContext.FUNCTION +import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode +import org.jetbrains.kotlin.resolve.scopes.LexicalScope +import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver +import org.jetbrains.kotlin.resolve.scopes.utils.collectDescriptorsFiltered +import org.jetbrains.kotlin.resolve.scopes.utils.findVariable +import org.jetbrains.kotlin.types.KotlinType + +private val counterpartNames = mapOf( + "apply" to "also", + "run" to "let", + "also" to "apply", + "let" to "run" +) + +class ScopeFunctionConversionInspection : AbstractKotlinInspection() { + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor { + return callExpressionVisitor { expression -> + val counterpartName = getCounterpart(expression) + if (counterpartName != null) { + holder.registerProblem( + expression.calleeExpression!!, + "Call can be replaced with another scope function", + ProblemHighlightType.INFORMATION, + if (counterpartName == "also" || counterpartName == "let") + ConvertScopeFunctionToParameter(counterpartName) + else + ConvertScopeFunctionToReceiver(counterpartName) + ) + } + + } + } +} + +private fun getCounterpart(expression: KtCallExpression): String? { + val callee = expression.calleeExpression as? KtNameReferenceExpression ?: return null + val calleeName = callee.getReferencedName() + val counterpartName = counterpartNames[calleeName] + val lambdaArgument = expression.lambdaArguments.singleOrNull() + if (counterpartName != null && lambdaArgument != null) { + if (lambdaArgument.getLambdaExpression().valueParameters.isNotEmpty()) { + return null + } + val bindingContext = callee.analyze(BodyResolveMode.PARTIAL) + val resolvedCall = callee.getResolvedCall(bindingContext) ?: return null + if (resolvedCall.resultingDescriptor.fqNameSafe.asString() == "kotlin.$calleeName" && + nameResolvesToStdlib(expression, bindingContext, counterpartName) + ) { + return counterpartName + } + } + return null +} + +private fun nameResolvesToStdlib(expression: KtCallExpression, bindingContext: BindingContext, name: String): Boolean { + val scope = expression.getResolutionScope(bindingContext) ?: return true + val descriptors = scope.collectDescriptorsFiltered(nameFilter = { it.asString() == name }) + return descriptors.isNotEmpty() && descriptors.all { it.fqNameSafe.asString() == "kotlin.$name" } +} + +class Replacement private constructor( + private val elementPointer: SmartPsiElementPointer, + private val replacementFactory: KtPsiFactory.(T) -> PsiElement +) { + companion object { + fun create(element: T, replacementFactory: KtPsiFactory.(T) -> PsiElement): Replacement { + return Replacement(element.createSmartPointer(), replacementFactory) + } + } + + fun apply(factory: KtPsiFactory) { + elementPointer.element?.let { + it.replace(factory.replacementFactory(it)) + } + } + + val endOffset + get() = elementPointer.element!!.endOffset +} + +class ReplacementCollection { + private lateinit var project: Project + private val replacements = mutableListOf>() + var createParameter: KtPsiFactory.() -> PsiElement? = { null } + var elementToRename: PsiElement? = null + + fun add(element: T, replacementFactory: KtPsiFactory.(T) -> PsiElement) { + project = element.project + replacements.add(Replacement.create(element, replacementFactory)) + } + + fun apply() { + if (replacements.isNotEmpty()) { + val factory = KtPsiFactory(project) + elementToRename = factory.createParameter() + + // Calls need to be processed in outside-in order + replacements.sortBy { it.endOffset } + + for (replacement in replacements) { + replacement.apply(factory) + } + } + } + + fun isNotEmpty() = replacements.isNotEmpty() +} + +abstract class ConvertScopeFunctionFix(private val counterpartName: String) : LocalQuickFix { + override fun getFamilyName() = "Convert to '$counterpartName'" + + override fun applyFix(project: Project, problemDescriptor: ProblemDescriptor) { + val callee = problemDescriptor.psiElement as KtNameReferenceExpression + val callExpression = callee.parent as? KtCallExpression ?: return + val bindingContext = callExpression.analyze() + + val lambda = callExpression.lambdaArguments.firstOrNull() ?: return + val functionLiteral = lambda.getLambdaExpression().functionLiteral + val lambdaDescriptor = bindingContext[FUNCTION, functionLiteral] ?: return + + val replacements = ReplacementCollection() + analyzeLambda(bindingContext, lambda, lambdaDescriptor, replacements) + callee.replace(KtPsiFactory(project).createExpression(counterpartName) as KtNameReferenceExpression) + if (replacements.isNotEmpty()) { + replacements.apply() + } + postprocessLambda(lambda) + + if (replacements.isNotEmpty() && replacements.elementToRename != null && !ApplicationManager.getApplication().isUnitTestMode) { + replacements.elementToRename!!.startInPlaceRename() + } + } + + protected abstract fun postprocessLambda(lambda: KtLambdaArgument) + + protected abstract fun analyzeLambda( + bindingContext: BindingContext, + lambda: KtLambdaArgument, + lambdaDescriptor: SimpleFunctionDescriptor, + replacements: ReplacementCollection + ) +} + +class ConvertScopeFunctionToParameter(counterpartName: String) : ConvertScopeFunctionFix(counterpartName) { + override fun analyzeLambda( + bindingContext: BindingContext, + lambda: KtLambdaArgument, + lambdaDescriptor: SimpleFunctionDescriptor, + replacements: ReplacementCollection + ) { + val project = lambda.project + val factory = KtPsiFactory(project) + val functionLiteral = lambda.getLambdaExpression().functionLiteral + val lambdaExtensionReceiver = lambdaDescriptor.extensionReceiverParameter + val lambdaDispatchReceiver = lambdaDescriptor.dispatchReceiverParameter + + var parameterName = "it" + val scopes = mutableSetOf() + if (needUniqueNameForParameter(lambda, scopes)) { + val parameterType = lambdaExtensionReceiver?.type ?: lambdaDispatchReceiver?.type + parameterName = findUniqueParameterName(parameterType, scopes) + replacements.createParameter = { + val lambdaParameterList = functionLiteral.getOrCreateParameterList() + val parameterToAdd = createLambdaParameterList(parameterName).parameters.first() + lambdaParameterList.addParameterBefore(parameterToAdd, lambdaParameterList.parameters.firstOrNull()) + } + } + + lambda.accept(object : KtTreeVisitorVoid() { + override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) { + super.visitSimpleNameExpression(expression) + val resolvedCall = expression.getResolvedCall(bindingContext) ?: return + val dispatchReceiverTarget = resolvedCall.dispatchReceiver?.getReceiverTargetDescriptor(bindingContext) + val extensionReceiverTarget = resolvedCall.extensionReceiver?.getReceiverTargetDescriptor(bindingContext) + if (dispatchReceiverTarget == lambdaDescriptor || extensionReceiverTarget == lambdaDescriptor) { + val parent = expression.parent + if (parent is KtCallExpression && expression == parent.calleeExpression) { + replacements.add(parent) { element -> + factory.createExpressionByPattern("$0.$1", parameterName, element) + } + } else if (parent is KtQualifiedExpression && parent.receiverExpression is KtThisExpression) { + // do nothing + } else { + val referencedName = expression.getReferencedName() + replacements.add(expression) { + createExpression("$parameterName.$referencedName") + } + } + } + } + + override fun visitThisExpression(expression: KtThisExpression) { + val resolvedCall = expression.getResolvedCall(bindingContext) ?: return + if (resolvedCall.resultingDescriptor == lambdaDispatchReceiver || + resolvedCall.resultingDescriptor == lambdaExtensionReceiver) { + replacements.add(expression) { createExpression(parameterName) } + } + } + }) + } + + override fun postprocessLambda(lambda: KtLambdaArgument) { + ShortenReferences { ShortenReferences.Options(removeThisLabels = true) }.process(lambda) { element -> + if (element is KtThisExpression && element.getLabelName() != null) + ShortenReferences.FilterResult.PROCESS + else + ShortenReferences.FilterResult.GO_INSIDE + } + } + + private fun needUniqueNameForParameter( + lambdaArgument: KtLambdaArgument, + scopes: MutableSet + ): Boolean { + val resolutionScope = lambdaArgument.getResolutionScope() + scopes.add(resolutionScope) + var needUniqueName = false + if (resolutionScope.findVariable(Name.identifier("it"), NoLookupLocation.FROM_IDE) != null) { + needUniqueName = true + // Don't return here - we still need to gather the list of nested scopes + } + + lambdaArgument.accept(object : KtTreeVisitorVoid() { + override fun visitDeclaration(dcl: KtDeclaration) { + super.visitDeclaration(dcl) + checkNeedUniqueName(dcl) + } + + override fun visitLambdaExpression(lambdaExpression: KtLambdaExpression) { + super.visitLambdaExpression(lambdaExpression) + lambdaExpression.bodyExpression?.statements?.firstOrNull()?.let { checkNeedUniqueName(it) } + } + + private fun checkNeedUniqueName(dcl: KtElement) { + val nestedResolutionScope = dcl.getResolutionScope() + scopes.add(nestedResolutionScope) + if (nestedResolutionScope.findVariable(Name.identifier("it"), NoLookupLocation.FROM_IDE) != null) { + needUniqueName = true + } + } + }) + + return needUniqueName + } + + + private fun findUniqueParameterName( + parameterType: KotlinType?, + resolutionScopes: Collection + ): String { + fun isNameUnique(parameterName: String): Boolean { + return resolutionScopes.none { it.findVariable(Name.identifier(parameterName), NoLookupLocation.FROM_IDE) != null } + } + + return if (parameterType != null) + KotlinNameSuggester.suggestNamesByType(parameterType, ::isNameUnique).first() + else { + KotlinNameSuggester.suggestNameByName("p", ::isNameUnique) + } + } +} + +class ConvertScopeFunctionToReceiver(counterpartName: String) : ConvertScopeFunctionFix(counterpartName) { + override fun analyzeLambda( + bindingContext: BindingContext, + lambda: KtLambdaArgument, + lambdaDescriptor: SimpleFunctionDescriptor, + replacements: ReplacementCollection + ) { + lambda.accept(object : KtTreeVisitorVoid() { + override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) { + super.visitSimpleNameExpression(expression) + if (expression.getReferencedName() == "it") { + val result = expression.resolveMainReferenceToDescriptors().singleOrNull() + if (result is ValueParameterDescriptor && result.containingDeclaration == lambdaDescriptor) { + replacements.add(expression) { createThisExpression() } + } + } else { + val resolvedCall = expression.getResolvedCall(bindingContext) ?: return + val dispatchReceiver = resolvedCall.dispatchReceiver + if (dispatchReceiver is ImplicitReceiver) { + val parent = expression.parent + val thisLabelName = dispatchReceiver.declarationDescriptor.getThisLabelName() + if (parent is KtCallExpression && expression == parent.calleeExpression) { + replacements.add(parent) { element -> + createExpressionByPattern("this@$0.$1", thisLabelName, element) + } + } else { + val referencedName = expression.getReferencedName() + replacements.add(expression) { + createExpression("this@$thisLabelName.$referencedName") + } + } + } + } + } + + override fun visitThisExpression(expression: KtThisExpression) { + val resolvedCall = expression.getResolvedCall(bindingContext) ?: return + val qualifierName = resolvedCall.resultingDescriptor.containingDeclaration.name + replacements.add(expression) { createThisExpression(qualifierName.asString()) } + } + }) + } + + override fun postprocessLambda(lambda: KtLambdaArgument) { + ShortenReferences { ShortenReferences.Options(removeThis = true, removeThisLabels = true) }.process(lambda) { element -> + if (element is KtThisExpression && element.getLabelName() != null) + ShortenReferences.FilterResult.PROCESS + else if (element is KtQualifiedExpression && element.receiverExpression is KtThisExpression) + ShortenReferences.FilterResult.PROCESS + else + ShortenReferences.FilterResult.GO_INSIDE + } + } +} + +private fun PsiElement.startInPlaceRename() { + val project = project + val document = containingFile.viewProvider.document ?: return + val editor = FileEditorManager.getInstance(project).selectedTextEditor ?: return + if (editor.document == document) { + PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(editor.document) + + editor.caretModel.moveToOffset(startOffset) + VariableInplaceRenameHandler().doRename(this, editor, null) + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/kotlinRefactoringUtil.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/kotlinRefactoringUtil.kt index 88978d1b09c..b2343e05ed1 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/kotlinRefactoringUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/kotlinRefactoringUtil.kt @@ -1,17 +1,6 @@ /* - * 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. + * Copyright 2000-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.refactoring @@ -835,6 +824,7 @@ internal fun DeclarationDescriptor.getThisLabelName(): String { if (this is AnonymousFunctionDescriptor) { val function = source.getPsi() as? KtFunction val argument = function?.parent as? KtValueArgument + ?: (function?.parent as? KtLambdaExpression)?.parent as? KtValueArgument val callElement = argument?.getStrictParentOfType() val callee = callElement?.calleeExpression as? KtSimpleNameExpression if (callee != null) return callee.text diff --git a/idea/testData/inspectionsLocal/scopeFunctions/.inspection b/idea/testData/inspectionsLocal/scopeFunctions/.inspection new file mode 100644 index 00000000000..175389c51cb --- /dev/null +++ b/idea/testData/inspectionsLocal/scopeFunctions/.inspection @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.inspections.ScopeFunctionConversionInspection \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/scopeFunctions/alsoToApply/simple.kt b/idea/testData/inspectionsLocal/scopeFunctions/alsoToApply/simple.kt new file mode 100644 index 00000000000..5762ea6a087 --- /dev/null +++ b/idea/testData/inspectionsLocal/scopeFunctions/alsoToApply/simple.kt @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +val x = "".also { + it.length +} diff --git a/idea/testData/inspectionsLocal/scopeFunctions/alsoToApply/simple.kt.after b/idea/testData/inspectionsLocal/scopeFunctions/alsoToApply/simple.kt.after new file mode 100644 index 00000000000..c08ae9b07e2 --- /dev/null +++ b/idea/testData/inspectionsLocal/scopeFunctions/alsoToApply/simple.kt.after @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +val x = "".apply { + length +} diff --git a/idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/doubleNestedLambdas.kt b/idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/doubleNestedLambdas.kt new file mode 100644 index 00000000000..853d70940fe --- /dev/null +++ b/idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/doubleNestedLambdas.kt @@ -0,0 +1,9 @@ +// WITH_RUNTIME + +val x = hashSetOf("abc").apply { + forEach { + forEach { + println(this) + } + } +} diff --git a/idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/doubleNestedLambdas.kt.after b/idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/doubleNestedLambdas.kt.after new file mode 100644 index 00000000000..02d8751511d --- /dev/null +++ b/idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/doubleNestedLambdas.kt.after @@ -0,0 +1,9 @@ +// WITH_RUNTIME + +val x = hashSetOf("abc").also { hashSet -> + hashSet.forEach { + hashSet.forEach { + println(hashSet) + } + } +} diff --git a/idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/innerLambda.kt b/idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/innerLambda.kt new file mode 100644 index 00000000000..90ffcdbe1b6 --- /dev/null +++ b/idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/innerLambda.kt @@ -0,0 +1,8 @@ +// WITH_RUNTIME +// FIX: Convert to 'also' + +val x = "".also { + "".apply { + this.length + it.length + } +} diff --git a/idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/innerLambda.kt.after b/idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/innerLambda.kt.after new file mode 100644 index 00000000000..917230b66fd --- /dev/null +++ b/idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/innerLambda.kt.after @@ -0,0 +1,8 @@ +// WITH_RUNTIME +// FIX: Convert to 'also' + +val x = "".also { + "".also { s -> + s.length + it.length + } +} diff --git a/idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/itInNestedLambda.kt b/idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/itInNestedLambda.kt new file mode 100644 index 00000000000..ae1a5a60f99 --- /dev/null +++ b/idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/itInNestedLambda.kt @@ -0,0 +1,7 @@ +// WITH_RUNTIME + +val x = hashSetOf("abc").apply { + forEach { + println(this) + } +} diff --git a/idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/itInNestedLambda.kt.after b/idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/itInNestedLambda.kt.after new file mode 100644 index 00000000000..04107772bc1 --- /dev/null +++ b/idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/itInNestedLambda.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME + +val x = hashSetOf("abc").also { hashSet -> + hashSet.forEach { + println(hashSet) + } +} diff --git a/idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/method.kt b/idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/method.kt new file mode 100644 index 00000000000..6da2ead8f37 --- /dev/null +++ b/idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/method.kt @@ -0,0 +1,6 @@ +// WITH_RUNTIME +// FIX: Convert to 'also' + +val x = hashSetOf().apply { + add("x") +} diff --git a/idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/method.kt.after b/idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/method.kt.after new file mode 100644 index 00000000000..15b6c3979bd --- /dev/null +++ b/idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/method.kt.after @@ -0,0 +1,6 @@ +// WITH_RUNTIME +// FIX: Convert to 'also' + +val x = hashSetOf().also { + it.add("x") +} diff --git a/idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/outerLambda.kt b/idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/outerLambda.kt new file mode 100644 index 00000000000..5a32a53dd5d --- /dev/null +++ b/idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/outerLambda.kt @@ -0,0 +1,9 @@ +// WITH_RUNTIME +// FIX: Convert to 'also' + +val x = "".apply { + "".apply { + this.length + length + } +} diff --git a/idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/outerLambda.kt.after b/idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/outerLambda.kt.after new file mode 100644 index 00000000000..ab8897ddb34 --- /dev/null +++ b/idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/outerLambda.kt.after @@ -0,0 +1,9 @@ +// WITH_RUNTIME +// FIX: Convert to 'also' + +val x = "".also { + "".apply { + this.length + length + } +} diff --git a/idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/simple.kt b/idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/simple.kt new file mode 100644 index 00000000000..205ca70898a --- /dev/null +++ b/idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/simple.kt @@ -0,0 +1,7 @@ +// WITH_RUNTIME +// FIX: Convert to 'also' + +val x = "".apply { + this.length + length +} diff --git a/idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/simple.kt.after b/idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/simple.kt.after new file mode 100644 index 00000000000..3deac90bcba --- /dev/null +++ b/idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/simple.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME +// FIX: Convert to 'also' + +val x = "".also { + it.length + it.length +} diff --git a/idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/thisQualifier.kt b/idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/thisQualifier.kt new file mode 100644 index 00000000000..1db942152cf --- /dev/null +++ b/idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/thisQualifier.kt @@ -0,0 +1,8 @@ +// WITH_RUNTIME +// FIX: Convert to 'also' + +class C { + val x = hashSetOf().apply { + add(this@C) + } +} diff --git a/idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/thisQualifier.kt.after b/idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/thisQualifier.kt.after new file mode 100644 index 00000000000..b408322298c --- /dev/null +++ b/idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/thisQualifier.kt.after @@ -0,0 +1,8 @@ +// WITH_RUNTIME +// FIX: Convert to 'also' + +class C { + val x = hashSetOf().also { + it.add(this) + } +} diff --git a/idea/testData/inspectionsLocal/scopeFunctions/letToRun/nestedLambda.kt b/idea/testData/inspectionsLocal/scopeFunctions/letToRun/nestedLambda.kt new file mode 100644 index 00000000000..f3d4fdc62ba --- /dev/null +++ b/idea/testData/inspectionsLocal/scopeFunctions/letToRun/nestedLambda.kt @@ -0,0 +1,7 @@ +// WITH_RUNTIME + +val x = hashSetOf("abc").let { + it.forEach { + println(it) + } +} diff --git a/idea/testData/inspectionsLocal/scopeFunctions/letToRun/nestedLambda.kt.after b/idea/testData/inspectionsLocal/scopeFunctions/letToRun/nestedLambda.kt.after new file mode 100644 index 00000000000..083955a0f1c --- /dev/null +++ b/idea/testData/inspectionsLocal/scopeFunctions/letToRun/nestedLambda.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME + +val x = hashSetOf("abc").run { + forEach { + println(it) + } +} diff --git a/idea/testData/inspectionsLocal/scopeFunctions/letToRun/outerThis.kt b/idea/testData/inspectionsLocal/scopeFunctions/letToRun/outerThis.kt new file mode 100644 index 00000000000..b69435a15de --- /dev/null +++ b/idea/testData/inspectionsLocal/scopeFunctions/letToRun/outerThis.kt @@ -0,0 +1,7 @@ +// WITH_RUNTIME + +class C { + val c = "abc".let { + println(it + this) + } +} diff --git a/idea/testData/inspectionsLocal/scopeFunctions/letToRun/outerThis.kt.after b/idea/testData/inspectionsLocal/scopeFunctions/letToRun/outerThis.kt.after new file mode 100644 index 00000000000..4d7d9bd6bc0 --- /dev/null +++ b/idea/testData/inspectionsLocal/scopeFunctions/letToRun/outerThis.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME + +class C { + val c = "abc".run { + println(this + this@C) + } +} diff --git a/idea/testData/inspectionsLocal/scopeFunctions/letToRun/qualifyThis.kt b/idea/testData/inspectionsLocal/scopeFunctions/letToRun/qualifyThis.kt new file mode 100644 index 00000000000..83b34362741 --- /dev/null +++ b/idea/testData/inspectionsLocal/scopeFunctions/letToRun/qualifyThis.kt @@ -0,0 +1,15 @@ +// WITH_RUNTIME + +class C { + fun foo() = "c" + val bar = "C" +} + +class D { + fun foo() = "d" + val bar = "D" + + val x = C().let { + foo() + it.foo() + bar + it.bar + } +} diff --git a/idea/testData/inspectionsLocal/scopeFunctions/letToRun/qualifyThis.kt.after b/idea/testData/inspectionsLocal/scopeFunctions/letToRun/qualifyThis.kt.after new file mode 100644 index 00000000000..ffc5d92e899 --- /dev/null +++ b/idea/testData/inspectionsLocal/scopeFunctions/letToRun/qualifyThis.kt.after @@ -0,0 +1,15 @@ +// WITH_RUNTIME + +class C { + fun foo() = "c" + val bar = "C" +} + +class D { + fun foo() = "d" + val bar = "D" + + val x = C().run { + this@D.foo() + foo() + this@D.bar + bar + } +} diff --git a/idea/testData/inspectionsLocal/scopeFunctions/letToRun/qualifyThisNoConflict.kt b/idea/testData/inspectionsLocal/scopeFunctions/letToRun/qualifyThisNoConflict.kt new file mode 100644 index 00000000000..3d7dfaf3d41 --- /dev/null +++ b/idea/testData/inspectionsLocal/scopeFunctions/letToRun/qualifyThisNoConflict.kt @@ -0,0 +1,15 @@ +// WITH_RUNTIME + +class C { + fun foo(s: String, s2: String = ""): String = "c" + val bar = "C" +} + +class D { + fun baz(s: String): String = "d" + val quux = "D" + + val x = C().let { + it.foo(baz(it.foo(quux + it.bar)), baz(it.bar)) + } +} diff --git a/idea/testData/inspectionsLocal/scopeFunctions/letToRun/qualifyThisNoConflict.kt.after b/idea/testData/inspectionsLocal/scopeFunctions/letToRun/qualifyThisNoConflict.kt.after new file mode 100644 index 00000000000..987f6d03453 --- /dev/null +++ b/idea/testData/inspectionsLocal/scopeFunctions/letToRun/qualifyThisNoConflict.kt.after @@ -0,0 +1,15 @@ +// WITH_RUNTIME + +class C { + fun foo(s: String, s2: String = ""): String = "c" + val bar = "C" +} + +class D { + fun baz(s: String): String = "d" + val quux = "D" + + val x = C().run { + foo(baz(foo(quux + bar)), baz(bar)) + } +} diff --git a/idea/testData/inspectionsLocal/scopeFunctions/letToRun/qualifyThisWithLambda.kt b/idea/testData/inspectionsLocal/scopeFunctions/letToRun/qualifyThisWithLambda.kt new file mode 100644 index 00000000000..3bfe7c7a13c --- /dev/null +++ b/idea/testData/inspectionsLocal/scopeFunctions/letToRun/qualifyThisWithLambda.kt @@ -0,0 +1,19 @@ +// WITH_RUNTIME + +class C { + fun foo() = "c" + val bar = "C" +} + +class D { + fun foo() = "d" + val bar = "D" + +} + +val d = D() +val x = d.apply { + C().let { + foo() + it.foo() + bar + it.bar + } +} diff --git a/idea/testData/inspectionsLocal/scopeFunctions/letToRun/qualifyThisWithLambda.kt.after b/idea/testData/inspectionsLocal/scopeFunctions/letToRun/qualifyThisWithLambda.kt.after new file mode 100644 index 00000000000..0779c05d46b --- /dev/null +++ b/idea/testData/inspectionsLocal/scopeFunctions/letToRun/qualifyThisWithLambda.kt.after @@ -0,0 +1,19 @@ +// WITH_RUNTIME + +class C { + fun foo() = "c" + val bar = "C" +} + +class D { + fun foo() = "d" + val bar = "D" + +} + +val d = D() +val x = d.apply { + C().run { + this@apply.foo() + foo() + this@apply.bar + bar + } +} diff --git a/idea/testData/inspectionsLocal/scopeFunctions/letToRun/qualifyThisWithLambdaNoConflict.kt b/idea/testData/inspectionsLocal/scopeFunctions/letToRun/qualifyThisWithLambdaNoConflict.kt new file mode 100644 index 00000000000..6f3f9b27918 --- /dev/null +++ b/idea/testData/inspectionsLocal/scopeFunctions/letToRun/qualifyThisWithLambdaNoConflict.kt @@ -0,0 +1,19 @@ +// WITH_RUNTIME + +class C { + fun foo() = "c" + val bar = "C" +} + +class D { + fun baz() = "d" + val quux = "D" + +} + +val d = D() +val x = d.apply { + C().let { + baz() + it.foo() + quux + it.bar + } +} diff --git a/idea/testData/inspectionsLocal/scopeFunctions/letToRun/qualifyThisWithLambdaNoConflict.kt.after b/idea/testData/inspectionsLocal/scopeFunctions/letToRun/qualifyThisWithLambdaNoConflict.kt.after new file mode 100644 index 00000000000..ca16a32433e --- /dev/null +++ b/idea/testData/inspectionsLocal/scopeFunctions/letToRun/qualifyThisWithLambdaNoConflict.kt.after @@ -0,0 +1,19 @@ +// WITH_RUNTIME + +class C { + fun foo() = "c" + val bar = "C" +} + +class D { + fun baz() = "d" + val quux = "D" + +} + +val d = D() +val x = d.apply { + C().run { + baz() + foo() + quux + bar + } +} diff --git a/idea/testData/inspectionsLocal/scopeFunctions/letToRun/simple.kt b/idea/testData/inspectionsLocal/scopeFunctions/letToRun/simple.kt new file mode 100644 index 00000000000..18dc9809f0b --- /dev/null +++ b/idea/testData/inspectionsLocal/scopeFunctions/letToRun/simple.kt @@ -0,0 +1,6 @@ +// WITH_RUNTIME +// FIX: Convert to 'run' + +val x = "".let { + it.length +} diff --git a/idea/testData/inspectionsLocal/scopeFunctions/letToRun/simple.kt.after b/idea/testData/inspectionsLocal/scopeFunctions/letToRun/simple.kt.after new file mode 100644 index 00000000000..8f58bd93179 --- /dev/null +++ b/idea/testData/inspectionsLocal/scopeFunctions/letToRun/simple.kt.after @@ -0,0 +1,6 @@ +// WITH_RUNTIME +// FIX: Convert to 'run' + +val x = "".run { + length +} diff --git a/idea/testData/inspectionsLocal/scopeFunctions/letToRun/thisInStringTemplate.kt b/idea/testData/inspectionsLocal/scopeFunctions/letToRun/thisInStringTemplate.kt new file mode 100644 index 00000000000..527e0236938 --- /dev/null +++ b/idea/testData/inspectionsLocal/scopeFunctions/letToRun/thisInStringTemplate.kt @@ -0,0 +1,7 @@ +// WITH_RUNTIME + +class C { + val c = "abc".let { + println("$it") + } +} diff --git a/idea/testData/inspectionsLocal/scopeFunctions/letToRun/thisInStringTemplate.kt.after b/idea/testData/inspectionsLocal/scopeFunctions/letToRun/thisInStringTemplate.kt.after new file mode 100644 index 00000000000..6d65e9d527b --- /dev/null +++ b/idea/testData/inspectionsLocal/scopeFunctions/letToRun/thisInStringTemplate.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME + +class C { + val c = "abc".run { + println("$this") + } +} diff --git a/idea/testData/inspectionsLocal/scopeFunctions/runToLet/simple.kt b/idea/testData/inspectionsLocal/scopeFunctions/runToLet/simple.kt new file mode 100644 index 00000000000..94784debe15 --- /dev/null +++ b/idea/testData/inspectionsLocal/scopeFunctions/runToLet/simple.kt @@ -0,0 +1,7 @@ +// WITH_RUNTIME +// FIX: Convert to 'let' + +val x = "".run { + this.length + length +} diff --git a/idea/testData/inspectionsLocal/scopeFunctions/runToLet/simple.kt.after b/idea/testData/inspectionsLocal/scopeFunctions/runToLet/simple.kt.after new file mode 100644 index 00000000000..74db8594d4c --- /dev/null +++ b/idea/testData/inspectionsLocal/scopeFunctions/runToLet/simple.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME +// FIX: Convert to 'let' + +val x = "".let { + it.length + it.length +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java index 580287ddf09..5931a92d115 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -3421,6 +3421,153 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest { } } + @TestMetadata("idea/testData/inspectionsLocal/scopeFunctions") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ScopeFunctions extends AbstractLocalInspectionTest { + public void testAllFilesPresentInScopeFunctions() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/scopeFunctions"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true); + } + + @TestMetadata("idea/testData/inspectionsLocal/scopeFunctions/alsoToApply") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class AlsoToApply extends AbstractLocalInspectionTest { + public void testAllFilesPresentInAlsoToApply() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/scopeFunctions/alsoToApply"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true); + } + + @TestMetadata("simple.kt") + public void testSimple() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/scopeFunctions/alsoToApply/simple.kt"); + doTest(fileName); + } + } + + @TestMetadata("idea/testData/inspectionsLocal/scopeFunctions/applyToAlso") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ApplyToAlso extends AbstractLocalInspectionTest { + public void testAllFilesPresentInApplyToAlso() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/scopeFunctions/applyToAlso"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true); + } + + @TestMetadata("doubleNestedLambdas.kt") + public void testDoubleNestedLambdas() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/doubleNestedLambdas.kt"); + doTest(fileName); + } + + @TestMetadata("innerLambda.kt") + public void testInnerLambda() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/innerLambda.kt"); + doTest(fileName); + } + + @TestMetadata("itInNestedLambda.kt") + public void testItInNestedLambda() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/itInNestedLambda.kt"); + doTest(fileName); + } + + @TestMetadata("method.kt") + public void testMethod() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/method.kt"); + doTest(fileName); + } + + @TestMetadata("outerLambda.kt") + public void testOuterLambda() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/outerLambda.kt"); + doTest(fileName); + } + + @TestMetadata("simple.kt") + public void testSimple() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/simple.kt"); + doTest(fileName); + } + + @TestMetadata("thisQualifier.kt") + public void testThisQualifier() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/thisQualifier.kt"); + doTest(fileName); + } + } + + @TestMetadata("idea/testData/inspectionsLocal/scopeFunctions/letToRun") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class LetToRun extends AbstractLocalInspectionTest { + public void testAllFilesPresentInLetToRun() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/scopeFunctions/letToRun"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true); + } + + @TestMetadata("nestedLambda.kt") + public void testNestedLambda() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/scopeFunctions/letToRun/nestedLambda.kt"); + doTest(fileName); + } + + @TestMetadata("outerThis.kt") + public void testOuterThis() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/scopeFunctions/letToRun/outerThis.kt"); + doTest(fileName); + } + + @TestMetadata("qualifyThis.kt") + public void testQualifyThis() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/scopeFunctions/letToRun/qualifyThis.kt"); + doTest(fileName); + } + + @TestMetadata("qualifyThisNoConflict.kt") + public void testQualifyThisNoConflict() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/scopeFunctions/letToRun/qualifyThisNoConflict.kt"); + doTest(fileName); + } + + @TestMetadata("qualifyThisWithLambda.kt") + public void testQualifyThisWithLambda() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/scopeFunctions/letToRun/qualifyThisWithLambda.kt"); + doTest(fileName); + } + + @TestMetadata("qualifyThisWithLambdaNoConflict.kt") + public void testQualifyThisWithLambdaNoConflict() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/scopeFunctions/letToRun/qualifyThisWithLambdaNoConflict.kt"); + doTest(fileName); + } + + @TestMetadata("simple.kt") + public void testSimple() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/scopeFunctions/letToRun/simple.kt"); + doTest(fileName); + } + + @TestMetadata("thisInStringTemplate.kt") + public void testThisInStringTemplate() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/scopeFunctions/letToRun/thisInStringTemplate.kt"); + doTest(fileName); + } + } + + @TestMetadata("idea/testData/inspectionsLocal/scopeFunctions/runToLet") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class RunToLet extends AbstractLocalInspectionTest { + public void testAllFilesPresentInRunToLet() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/scopeFunctions/runToLet"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true); + } + + @TestMetadata("simple.kt") + public void testSimple() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/scopeFunctions/runToLet/simple.kt"); + doTest(fileName); + } + } + } + @TestMetadata("idea/testData/inspectionsLocal/selfAssignment") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)