diff options
Diffstat (limited to 'plugins/kotlin/frontend-independent/src/org/jetbrains/kotlin')
8 files changed, 253 insertions, 60 deletions
diff --git a/plugins/kotlin/frontend-independent/src/org/jetbrains/kotlin/idea/findUsages/KotlinFindUsagesSupport.kt b/plugins/kotlin/frontend-independent/src/org/jetbrains/kotlin/idea/findUsages/KotlinFindUsagesSupport.kt index f05150b9bd4d..2f456b737469 100644 --- a/plugins/kotlin/frontend-independent/src/org/jetbrains/kotlin/idea/findUsages/KotlinFindUsagesSupport.kt +++ b/plugins/kotlin/frontend-independent/src/org/jetbrains/kotlin/idea/findUsages/KotlinFindUsagesSupport.kt @@ -3,6 +3,7 @@ package org.jetbrains.kotlin.idea.findUsages import com.intellij.openapi.project.Project +import com.intellij.psi.PsiConstructorCall import com.intellij.psi.PsiElement import com.intellij.psi.PsiReference import com.intellij.psi.search.GlobalSearchScope @@ -12,6 +13,7 @@ import org.jetbrains.kotlin.psi.KtClassOrObject import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.KtObjectDeclaration import org.jetbrains.kotlin.psi.KtParameter +import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType interface KotlinFindUsagesSupport { @@ -33,8 +35,14 @@ interface KotlinFindUsagesSupport { fun tryRenderDeclarationCompactStyle(declaration: KtDeclaration): String? = getInstance(declaration.project).tryRenderDeclarationCompactStyle(declaration) - fun PsiReference.isConstructorUsage(ktClassOrObject: KtClassOrObject): Boolean = - getInstance(ktClassOrObject.project).isConstructorUsage(this, ktClassOrObject) + fun PsiReference.isConstructorUsage(ktClassOrObject: KtClassOrObject): Boolean { + fun isJavaConstructorUsage(): Boolean { + val call = element.getNonStrictParentOfType<PsiConstructorCall>() + return call == element.parent && call?.resolveConstructor()?.containingClass?.navigationElement == ktClassOrObject + } + + return isJavaConstructorUsage() || getInstance(ktClassOrObject.project).isKotlinConstructorUsage(this, ktClassOrObject) + } fun getSuperMethods(declaration: KtDeclaration, ignore: Collection<PsiElement>?) : List<PsiElement> = getInstance(declaration.project).getSuperMethods(declaration, ignore) @@ -51,9 +59,9 @@ interface KotlinFindUsagesSupport { fun tryRenderDeclarationCompactStyle(declaration: KtDeclaration): String? - fun isConstructorUsage(psiReference: PsiReference, ktClassOrObject: KtClassOrObject): Boolean + fun isKotlinConstructorUsage(psiReference: PsiReference, ktClassOrObject: KtClassOrObject): Boolean fun getSuperMethods(declaration: KtDeclaration, ignore: Collection<PsiElement>?) : List<PsiElement> fun sourcesAndLibraries(delegate: GlobalSearchScope, project: Project): GlobalSearchScope -}
\ No newline at end of file +} diff --git a/plugins/kotlin/frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/AddElseBranchFix.kt b/plugins/kotlin/frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/AddElseBranchFix.kt new file mode 100644 index 000000000000..addcad5b933d --- /dev/null +++ b/plugins/kotlin/frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/AddElseBranchFix.kt @@ -0,0 +1,92 @@ +// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. + +package org.jetbrains.kotlin.idea.quickfix + +import com.intellij.codeInsight.CodeInsightUtilCore +import com.intellij.codeInsight.intention.IntentionAction +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiErrorElement +import com.intellij.psi.util.elementType +import org.jetbrains.kotlin.KtNodeTypes +import org.jetbrains.kotlin.idea.KotlinBundle +import org.jetbrains.kotlin.idea.util.executeEnterHandler +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.endOffset +import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType +import org.jetbrains.kotlin.utils.addToStdlib.safeAs + +sealed class AddElseBranchFix<T : KtExpression>(element: T) : KotlinPsiOnlyQuickFixAction<T>(element) { + override fun getFamilyName() = KotlinBundle.message("fix.add.else.branch.when") + override fun getText() = familyName + + abstract override fun isAvailable(project: Project, editor: Editor?, file: KtFile): Boolean + + abstract override fun invoke(project: Project, editor: Editor?, file: KtFile) +} + +class AddWhenElseBranchFix(element: KtWhenExpression) : AddElseBranchFix<KtWhenExpression>(element) { + override fun isAvailable(project: Project, editor: Editor?, file: KtFile): Boolean = element?.closeBrace != null + + override fun invoke(project: Project, editor: Editor?, file: KtFile) { + val element = element ?: return + val whenCloseBrace = element.closeBrace ?: return + val entry = KtPsiFactory(file).createWhenEntry("else -> {}") + CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(element.addBefore(entry, whenCloseBrace))?.endOffset?.let { offset -> + editor?.caretModel?.moveToOffset(offset - 1) + } + } + + companion object : QuickFixesPsiBasedFactory<PsiElement>(PsiElement::class, PsiElementSuitabilityCheckers.ALWAYS_SUITABLE) { + override fun doCreateQuickFix(psiElement: PsiElement): List<IntentionAction> { + return listOfNotNull(psiElement.getNonStrictParentOfType<KtWhenExpression>()?.let(::AddWhenElseBranchFix)) + } + } +} + +class AddIfElseBranchFix(element: KtIfExpression) : AddElseBranchFix<KtIfExpression>(element) { + override fun isAvailable(project: Project, editor: Editor?, file: KtFile): Boolean { + val ktIfExpression = element ?: return false + return ktIfExpression.`else` == null && + ktIfExpression.condition != null && + ktIfExpression.children.firstOrNull { it.elementType == KtNodeTypes.THEN }?.firstChild !is PsiErrorElement + } + + override fun invoke(project: Project, editor: Editor?, file: KtFile) { + val element = element ?: return + val withBraces = element.then is KtBlockExpression + val psiFactory = KtPsiFactory(file) + val newIf = psiFactory.createExpression( + if (withBraces) { + "if (true) {} else {}" + } else { + "if (true) 2 else TODO()" + } + ) as KtIfExpression + + element.addRange(newIf.then?.parent?.nextSibling, newIf.`else`?.parent) + editor?.caretModel?.currentCaret?.let { caret -> + if (withBraces) { + caret.moveToOffset(element.endOffset - 1) + val documentManager = PsiDocumentManager.getInstance(project) + documentManager.getDocument(element.containingFile)?.let { doc -> + documentManager.doPostponedOperationsAndUnblockDocument(doc) + editor.executeEnterHandler() + } + } else { + element.`else`?.textRange?.let { + caret.moveToOffset(it.startOffset) + caret.setSelection(it.startOffset, it.endOffset) + } + } + } + } + + companion object : QuickFixesPsiBasedFactory<PsiElement>(PsiElement::class, PsiElementSuitabilityCheckers.ALWAYS_SUITABLE) { + override fun doCreateQuickFix(psiElement: PsiElement): List<IntentionAction> { + return listOfNotNull(psiElement.parent?.safeAs<KtIfExpression>()?.let(::AddIfElseBranchFix)) + } + } +} diff --git a/plugins/kotlin/frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/AddWhenElseBranchFix.kt b/plugins/kotlin/frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/AddWhenElseBranchFix.kt deleted file mode 100644 index fb297eaef6c0..000000000000 --- a/plugins/kotlin/frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/AddWhenElseBranchFix.kt +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. - -package org.jetbrains.kotlin.idea.quickfix - -import com.intellij.codeInsight.CodeInsightUtilCore -import com.intellij.codeInsight.intention.IntentionAction -import com.intellij.openapi.editor.Editor -import com.intellij.openapi.project.Project -import com.intellij.psi.PsiElement -import org.jetbrains.kotlin.idea.KotlinBundle -import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.kotlin.psi.KtPsiFactory -import org.jetbrains.kotlin.psi.KtWhenExpression -import org.jetbrains.kotlin.psi.psiUtil.endOffset -import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType - -class AddWhenElseBranchFix(element: KtWhenExpression) : KotlinPsiOnlyQuickFixAction<KtWhenExpression>(element) { - override fun getFamilyName() = KotlinBundle.message("fix.add.else.branch.when") - override fun getText() = familyName - - override fun isAvailable(project: Project, editor: Editor?, file: KtFile): Boolean = element?.closeBrace != null - - override fun invoke(project: Project, editor: Editor?, file: KtFile) { - val element = element ?: return - val whenCloseBrace = element.closeBrace ?: return - val entry = KtPsiFactory(file).createWhenEntry("else -> {}") - CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(element.addBefore(entry, whenCloseBrace))?.endOffset?.let { offset -> - editor?.caretModel?.moveToOffset(offset - 1) - } - } - - companion object : QuickFixesPsiBasedFactory<PsiElement>(PsiElement::class, PsiElementSuitabilityCheckers.ALWAYS_SUITABLE) { - override fun doCreateQuickFix(psiElement: PsiElement): List<IntentionAction> { - return listOfNotNull(psiElement.getNonStrictParentOfType<KtWhenExpression>()?.let(::AddWhenElseBranchFix)) - } - } -} diff --git a/plugins/kotlin/frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/RemoveWhenBranchFix.kt b/plugins/kotlin/frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/RemoveWhenBranchFix.kt index 1690b5aa73ef..e2fe00d0735f 100644 --- a/plugins/kotlin/frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/RemoveWhenBranchFix.kt +++ b/plugins/kotlin/frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/RemoveWhenBranchFix.kt @@ -9,9 +9,7 @@ import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile -import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.idea.KotlinBundle -import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtWhenEntry import org.jetbrains.kotlin.psi.psiUtil.getParentOfType @@ -33,10 +31,11 @@ class RemoveWhenBranchFix( companion object : QuickFixesPsiBasedFactory<PsiElement>(PsiElement::class, PsiElementSuitabilityCheckers.ALWAYS_SUITABLE) { override fun doCreateQuickFix(psiElement: PsiElement): List<IntentionAction> { val whenEntry = psiElement.getParentOfType<KtWhenEntry>(strict = false) - ?.takeIf { it.conditions.size == 1 } - ?: return emptyList() + if (whenEntry != null && (whenEntry.isElse || whenEntry.conditions.size == 1)) { + return listOf(RemoveWhenBranchFix(whenEntry)) + } - return listOfNotNull(RemoveWhenBranchFix(whenEntry)) + return emptyList() } } }
\ No newline at end of file diff --git a/plugins/kotlin/frontend-independent/src/org/jetbrains/kotlin/idea/references/ReadWriteAccessChecker.kt b/plugins/kotlin/frontend-independent/src/org/jetbrains/kotlin/idea/references/ReadWriteAccessChecker.kt index ccd20a5b3eb3..72f964c3f80d 100644 --- a/plugins/kotlin/frontend-independent/src/org/jetbrains/kotlin/idea/references/ReadWriteAccessChecker.kt +++ b/plugins/kotlin/frontend-independent/src/org/jetbrains/kotlin/idea/references/ReadWriteAccessChecker.kt @@ -3,22 +3,46 @@ package org.jetbrains.kotlin.idea.references import com.intellij.openapi.components.service -import org.jetbrains.kotlin.psi.KtBinaryExpression -import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.getAssignmentByLHS +import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelectorOrThis import org.jetbrains.kotlin.resolve.references.ReferenceAccess +import org.jetbrains.kotlin.utils.addToStdlib.constant interface ReadWriteAccessChecker { fun readWriteAccessWithFullExpressionByResolve(assignment: KtBinaryExpression): Pair<ReferenceAccess, KtExpression>? - @Suppress("DEPRECATION") fun readWriteAccessWithFullExpression( targetExpression: KtExpression, useResolveForReadWrite: Boolean - ): Pair<ReferenceAccess, KtExpression> = - if (useResolveForReadWrite) - targetExpression.readWriteAccessWithFullExpressionWithPossibleResolve(::readWriteAccessWithFullExpressionByResolve) + ): Pair<ReferenceAccess, KtExpression> { + var expression = targetExpression.getQualifiedExpressionForSelectorOrThis() + loop@ while (true) { + when (val parent = expression.parent) { + is KtParenthesizedExpression, is KtAnnotatedExpression, is KtLabeledExpression -> expression = parent as KtExpression + else -> break@loop + } + } + + val assignment = expression.getAssignmentByLHS() + if (assignment != null) { + return when (assignment.operationToken) { + KtTokens.EQ -> ReferenceAccess.WRITE to assignment + + else -> { + (if (useResolveForReadWrite) readWriteAccessWithFullExpressionByResolve(assignment) else null) + ?: (ReferenceAccess.READ_WRITE to assignment) + } + } + } + + val unaryExpression = expression.parent as? KtUnaryExpression + return if (unaryExpression != null && unaryExpression.operationToken in constant { setOf(KtTokens.PLUSPLUS, KtTokens.MINUSMINUS) }) + ReferenceAccess.READ_WRITE to unaryExpression else - targetExpression.readWriteAccessWithFullExpressionWithPossibleResolve(readWriteAccessWithFullExpressionByResolve = { null }) + ReferenceAccess.READ to expression + } companion object { fun getInstance(): ReadWriteAccessChecker = service() diff --git a/plugins/kotlin/frontend-independent/src/org/jetbrains/kotlin/idea/search/KotlinSearchSupport.kt b/plugins/kotlin/frontend-independent/src/org/jetbrains/kotlin/idea/search/KotlinSearchSupport.kt index dff3b26b24f4..80558e011c53 100644 --- a/plugins/kotlin/frontend-independent/src/org/jetbrains/kotlin/idea/search/KotlinSearchSupport.kt +++ b/plugins/kotlin/frontend-independent/src/org/jetbrains/kotlin/idea/search/KotlinSearchSupport.kt @@ -8,10 +8,13 @@ import com.intellij.openapi.util.NlsSafe import com.intellij.psi.* import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.SearchScope +import org.jetbrains.kotlin.asJava.classes.KtLightClass import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReferencesSearchOptions import org.jetbrains.kotlin.idea.util.application.getServiceSafe import org.jetbrains.kotlin.idea.util.application.runReadAction +import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.resolve.DataClassResolver import org.jetbrains.kotlin.resolve.ImportPath interface KotlinSearchUsagesSupport { @@ -32,10 +35,22 @@ interface KotlinSearchUsagesSupport { val PsiClass.isSamInterface: Boolean get() = getInstance(project).isSamInterface(this) - fun <T : PsiNamedElement> List<T>.filterDataClassComponentsIfDisabled(kotlinOptions: KotlinReferencesSearchOptions): List<T> = - firstOrNull()?.let { - getInstance(it.project).filterDataClassComponentsIfDisabled(this, kotlinOptions) - } ?: this + fun <T : PsiNamedElement> List<T>.filterDataClassComponentsIfDisabled(kotlinOptions: KotlinReferencesSearchOptions): List<T> { + fun PsiNamedElement.isComponentElement(): Boolean { + if (this !is PsiMethod) return false + + val dataClassParent = ((parent as? KtLightClass)?.kotlinOrigin as? KtClass)?.isData() == true + if (!dataClassParent) return false + + if (!Name.isValidIdentifier(name)) return false + val nameIdentifier = Name.identifier(name) + if (!DataClassResolver.isComponentLike(nameIdentifier)) return false + + return true + } + + return if (kotlinOptions.searchForComponentConventions) this else filter { !it.isComponentElement() } + } fun PsiReference.isCallableOverrideUsage(declaration: KtNamedDeclaration): Boolean = getInstance(declaration.project).isCallableOverrideUsage(this, declaration) @@ -121,8 +136,6 @@ interface KotlinSearchUsagesSupport { fun isSamInterface(psiClass: PsiClass): Boolean - fun <T : PsiNamedElement> filterDataClassComponentsIfDisabled(elements: List<T>, kotlinOptions: KotlinReferencesSearchOptions): List<T> - fun isCallableOverrideUsage(reference: PsiReference, declaration: KtNamedDeclaration): Boolean fun isUsageInContainingDeclaration(reference: PsiReference, declaration: KtNamedDeclaration): Boolean @@ -176,4 +189,4 @@ interface KotlinSearchUsagesSupport { fun createConstructorHandle(ktDeclaration: KtDeclaration): ConstructorCallHandle fun createConstructorHandle(psiMethod: PsiMethod): ConstructorCallHandle -}
\ No newline at end of file +} diff --git a/plugins/kotlin/frontend-independent/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinTargetElementEvaluator.kt b/plugins/kotlin/frontend-independent/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinTargetElementEvaluator.kt new file mode 100644 index 000000000000..9cb96071066a --- /dev/null +++ b/plugins/kotlin/frontend-independent/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinTargetElementEvaluator.kt @@ -0,0 +1,81 @@ +// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. + +package org.jetbrains.kotlin.idea.search.ideaExtensions + +import com.intellij.codeInsight.JavaTargetElementEvaluator +import com.intellij.codeInsight.TargetElementEvaluatorEx +import com.intellij.codeInsight.TargetElementUtil +import com.intellij.codeInsight.TargetElementUtilExtender +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.intellij.psi.PsiReference +import com.intellij.util.BitUtil +import org.jetbrains.kotlin.idea.references.KtDestructuringDeclarationReference +import org.jetbrains.kotlin.idea.references.KtSimpleNameReference +import org.jetbrains.kotlin.idea.references.mainReference +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.* + +abstract class KotlinTargetElementEvaluator : TargetElementEvaluatorEx, TargetElementUtilExtender { + companion object { + const val DO_NOT_UNWRAP_LABELED_EXPRESSION = 0x100 + const val BYPASS_IMPORT_ALIAS = 0x200 + } + + // Place caret after the open curly brace in lambda for generated 'it' + abstract fun findLambdaOpenLBraceForGeneratedIt(ref: PsiReference): PsiElement? + + // Navigate to receiver element for this in extension declaration + abstract fun findReceiverForThisInExtensionFunction(ref: PsiReference): PsiElement? + + override fun getAdditionalDefinitionSearchFlags() = 0 + + override fun getAdditionalReferenceSearchFlags() = DO_NOT_UNWRAP_LABELED_EXPRESSION or BYPASS_IMPORT_ALIAS + + override fun getAllAdditionalFlags() = additionalDefinitionSearchFlags + additionalReferenceSearchFlags + + override fun includeSelfInGotoImplementation(element: PsiElement): Boolean = !(element is KtClass && element.isAbstract()) + + override fun getElementByReference(ref: PsiReference, flags: Int): PsiElement? { + if (ref is KtSimpleNameReference && ref.expression is KtLabelReferenceExpression) { + val refTarget = ref.resolve() as? KtExpression ?: return null + if (!BitUtil.isSet(flags, DO_NOT_UNWRAP_LABELED_EXPRESSION)) { + return refTarget.getLabeledParent(ref.expression.getReferencedName()) ?: refTarget + } + return refTarget + } + + if (!BitUtil.isSet(flags, BYPASS_IMPORT_ALIAS)) { + (ref.element as? KtSimpleNameExpression)?.mainReference?.getImportAlias()?.let { return it } + } + + // prefer destructing declaration entry to its target if element name is accepted + if (ref is KtDestructuringDeclarationReference && BitUtil.isSet(flags, TargetElementUtil.ELEMENT_NAME_ACCEPTED)) { + return ref.element + } + + val refExpression = ref.element as? KtSimpleNameExpression + val calleeExpression = refExpression?.getParentOfTypeAndBranch<KtCallElement> { calleeExpression } + if (calleeExpression != null) { + (ref.resolve() as? KtConstructor<*>)?.let { + return if (flags and JavaTargetElementEvaluator().additionalReferenceSearchFlags != 0) it else it.containingClassOrObject + } + } + + if (BitUtil.isSet(flags, TargetElementUtil.REFERENCED_ELEMENT_ACCEPTED)) { + return findLambdaOpenLBraceForGeneratedIt(ref) + ?: findReceiverForThisInExtensionFunction(ref) + } + + return null + } + + override fun isIdentifierPart(file: PsiFile, text: CharSequence, offset: Int): Boolean { + val elementAtCaret = file.findElementAt(offset) + + if (elementAtCaret?.node?.elementType == KtTokens.IDENTIFIER) return true + // '(' is considered identifier part if it belongs to primary constructor without 'constructor' keyword + return elementAtCaret?.getNonStrictParentOfType<KtPrimaryConstructor>()?.textOffset == offset + } +} diff --git a/plugins/kotlin/frontend-independent/src/org/jetbrains/kotlin/idea/util/editorUtils.kt b/plugins/kotlin/frontend-independent/src/org/jetbrains/kotlin/idea/util/editorUtils.kt new file mode 100644 index 000000000000..a304a606bcf8 --- /dev/null +++ b/plugins/kotlin/frontend-independent/src/org/jetbrains/kotlin/idea/util/editorUtils.kt @@ -0,0 +1,13 @@ +// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.util + +import com.intellij.ide.DataManager +import com.intellij.openapi.actionSystem.IdeActions +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.editor.actionSystem.EditorActionManager + +fun Editor.executeEnterHandler() { + EditorActionManager.getInstance() + .getActionHandler(IdeActions.ACTION_EDITOR_ENTER) + .execute(/* editor = */ this, /* contextCaret = */ null, /* dataContext = */ DataManager.getInstance().getDataContext(component)) +} |