summaryrefslogtreecommitdiff
path: root/plugins/kotlin/fir/src/org/jetbrains/kotlin/idea/findUsages/KotlinFindUsagesSupportFirImpl.kt
blob: d35b732402e0b3f36dc8dd4f170973162e11c450 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
// 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.findUsages

import com.intellij.ide.IdeBundle
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.Messages
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiReference
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.util.Processor
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.analyseInModalWindow
import org.jetbrains.kotlin.analysis.api.calls.*
import org.jetbrains.kotlin.analysis.api.symbols.KtCallableSymbol
import org.jetbrains.kotlin.analysis.api.symbols.markers.KtNamedSymbol
import org.jetbrains.kotlin.analysis.api.symbols.markers.KtSymbolWithKind
import org.jetbrains.kotlin.idea.KotlinBundle
import org.jetbrains.kotlin.idea.core.util.showYesNoCancelDialog
import org.jetbrains.kotlin.idea.refactoring.CHECK_SUPER_METHODS_YES_NO_DIALOG
import org.jetbrains.kotlin.idea.refactoring.formatPsiClass
import org.jetbrains.kotlin.idea.references.KtInvokeFunctionReference
import org.jetbrains.kotlin.idea.util.withResolvedCall
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.SpecialNames
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType

class KotlinFindUsagesSupportFirImpl : KotlinFindUsagesSupport {
    override fun processCompanionObjectInternalReferences(
        companionObject: KtObjectDeclaration,
        referenceProcessor: Processor<PsiReference>
    ): Boolean {
        val klass = companionObject.getStrictParentOfType<KtClass>() ?: return true
        return !klass.anyDescendantOfType(fun(element: KtElement): Boolean {
            if (element == companionObject) return false
            return withResolvedCall(element) { call ->
                if (callReceiverRefersToCompanionObject(call, companionObject)) {
                    element.references.any {
                        // We get both a simple named reference and an invoke function
                        // reference for all function calls. We want the named reference.
                        //
                        // TODO: with FE1.0 the check for reference type is not needed.
                        // With FE1.0 two references that point to the same PSI are
                        // obtained and one is filtered out by the reference processor.
                        // We should make FIR references behave the same.
                        it !is KtInvokeFunctionReference && !referenceProcessor.process(it)
                    }
                } else {
                    false
                }
            } ?: false
        })
    }

    private fun KtAnalysisSession.callReceiverRefersToCompanionObject(call: KtCall, companionObject: KtObjectDeclaration): Boolean {
        if (call !is KtCallableMemberCall<*, *>) return false
        val dispatchReceiver = call.partiallyAppliedSymbol.dispatchReceiver
        val extensionReceiver = call.partiallyAppliedSymbol.extensionReceiver
        val companionObjectSymbol = companionObject.getSymbol()
        return (dispatchReceiver as? KtImplicitReceiverValue)?.symbol == companionObjectSymbol ||
                    (extensionReceiver as? KtImplicitReceiverValue)?.symbol == companionObjectSymbol
    }

    override fun isDataClassComponentFunction(element: KtParameter): Boolean {
        // TODO: implement this
        return false
    }

    override fun getTopMostOverriddenElementsToHighlight(target: PsiElement): List<PsiElement> {
        // TODO: implement this
        return emptyList()
    }

    override fun tryRenderDeclarationCompactStyle(declaration: KtDeclaration): String? {
        // TODO: implement this
        return (declaration as? KtNamedDeclaration)?.name ?: "SUPPORT FOR FIR"
    }

    override fun isKotlinConstructorUsage(psiReference: PsiReference, ktClassOrObject: KtClassOrObject): Boolean {
        val element = psiReference.element
        if (element !is KtElement) return false

        val constructorCalleeExpression = element.getNonStrictParentOfType<KtConstructorCalleeExpression>() ?: return false
        return withResolvedCall(constructorCalleeExpression) { call ->
            when (call) {
                is KtDelegatedConstructorCall -> {
                    val constructedClassSymbol = call.symbol.containingClassIdIfNonLocal?.getCorrespondingToplevelClassOrObjectSymbol()
                    constructedClassSymbol == ktClassOrObject.getClassOrObjectSymbol()
                }
                else -> false
            }
        } ?: false
    }

    override fun getSuperMethods(declaration: KtDeclaration, ignore: Collection<PsiElement>?): List<PsiElement> {
        // TODO: implement this
        return emptyList()
    }

    private fun checkSuperMethods(declaration: KtDeclaration, ignore: Collection<PsiElement>?, actionString: String): List<PsiElement> {

        if (!declaration.hasModifier(KtTokens.OVERRIDE_KEYWORD)) return listOf(declaration)

        data class AnalyzedModel(
            val declaredClassRender: String,
            val overriddenDeclarationsAndRenders: Map<PsiElement, String>
        )

        fun getClassDescription(overriddenElement: PsiElement, containingSymbol: KtSymbolWithKind?): String =
            when (overriddenElement) {
                is KtNamedFunction, is KtProperty, is KtParameter -> (containingSymbol as? KtNamedSymbol)?.name?.asString() ?: "Unknown"  //TODO render symbols
                is PsiMethod -> {
                    val psiClass = overriddenElement.containingClass ?: error("Invalid element: ${overriddenElement.text}")
                    formatPsiClass(psiClass, markAsJava = true, inCode = false)
                }
                else -> error("Unexpected element: ${overriddenElement.getElementTextWithContext()}")
            }.let { "    $it\n" }


        val analyzeResult = analyseInModalWindow(declaration, KotlinBundle.message("find.usages.progress.text.declaration.superMethods")) {
            (declaration.getSymbol() as? KtCallableSymbol)?.let { callableSymbol ->
                callableSymbol.originalContainingClassForOverride?.let { containingClass ->
                    val overriddenSymbols = callableSymbol.getAllOverriddenSymbols()

                    val renderToPsi = overriddenSymbols.mapNotNull {
                        it.psi?.let { psi ->
                            psi to getClassDescription(psi, it.originalContainingClassForOverride)
                        }
                    }

                    val filteredDeclarations =
                        if (ignore != null) renderToPsi.filter { ignore.contains(it.first) } else renderToPsi

                    val renderedClass = containingClass.name?.asString() ?: SpecialNames.ANONYMOUS_STRING //TODO render class

                    AnalyzedModel(renderedClass, filteredDeclarations.toMap())
                }
            }
        } ?: return listOf(declaration)

        if (analyzeResult.overriddenDeclarationsAndRenders.isEmpty()) return listOf(declaration)

        val message = KotlinBundle.message(
            "override.declaration.x.overrides.y.in.class.list",
            analyzeResult.declaredClassRender,
            "\n${analyzeResult.overriddenDeclarationsAndRenders.values.joinToString(separator = "")}",
            actionString
        )

        val exitCode = showYesNoCancelDialog(
            CHECK_SUPER_METHODS_YES_NO_DIALOG,
            declaration.project, message, IdeBundle.message("title.warning"), Messages.getQuestionIcon(), Messages.YES
        )

        return when (exitCode) {
            Messages.YES -> listOf(declaration) + analyzeResult.overriddenDeclarationsAndRenders.keys
            Messages.NO -> listOf(declaration)
            else -> emptyList()
        }
    }

    override fun sourcesAndLibraries(delegate: GlobalSearchScope, project: Project): GlobalSearchScope {
        return delegate
    }
}