Different modes of search
Down-shift to plain search in non-test mode if unknown reference encountered Internal action to check search algorithm
This commit is contained in:
+25
-12
@@ -17,6 +17,7 @@
|
||||
package org.jetbrains.kotlin.idea.search.usagesSearch
|
||||
|
||||
import com.intellij.lang.java.JavaLanguage
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.search.*
|
||||
import com.intellij.psi.search.searches.ClassInheritorsSearch
|
||||
@@ -39,6 +40,7 @@ import org.jetbrains.kotlin.idea.references.KtDestructuringDeclarationReference
|
||||
import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinRequestResultProcessor
|
||||
import org.jetbrains.kotlin.idea.search.restrictToKotlinSources
|
||||
import org.jetbrains.kotlin.idea.search.unionSafe
|
||||
import org.jetbrains.kotlin.idea.search.usagesSearch.DestructuringDeclarationUsageSearch.*
|
||||
import org.jetbrains.kotlin.idea.util.FuzzyType
|
||||
import org.jetbrains.kotlin.idea.util.fuzzyExtensionReceiverType
|
||||
import org.jetbrains.kotlin.idea.util.toFuzzyType
|
||||
@@ -52,6 +54,12 @@ import org.jetbrains.kotlin.util.isValidOperator
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
|
||||
import java.util.*
|
||||
|
||||
enum class DestructuringDeclarationUsageSearch {
|
||||
ALWAYS_SMART, ALWAYS_PLAIN, PLAIN_WHEN_NEEDED
|
||||
}
|
||||
|
||||
var destructuringDeclarationUsageSearchMode = if (ApplicationManager.getApplication().isUnitTestMode) ALWAYS_SMART else PLAIN_WHEN_NEEDED
|
||||
|
||||
//TODO: compiled code
|
||||
//TODO: check if it's too expensive
|
||||
|
||||
@@ -72,8 +80,12 @@ fun findDestructuringDeclarationUsages(
|
||||
consumer: Processor<PsiReference>,
|
||||
optimizer: SearchRequestCollector
|
||||
) {
|
||||
// for local scope it's faster to use plain search
|
||||
if (scope is LocalSearchScope) {
|
||||
val usePlainSearch = when (destructuringDeclarationUsageSearchMode) {
|
||||
ALWAYS_SMART -> false
|
||||
ALWAYS_PLAIN -> true
|
||||
PLAIN_WHEN_NEEDED -> scope is LocalSearchScope // for local scope it's faster to use plain search
|
||||
}
|
||||
if (usePlainSearch) {
|
||||
doPlainSearch(ktDeclaration, scope, optimizer)
|
||||
return
|
||||
}
|
||||
@@ -130,17 +142,18 @@ private class Processor(
|
||||
val classesToSearch = listOf(psiClass) + ClassInheritorsSearch.search(parameters).findAll()
|
||||
|
||||
for (classToSearch in classesToSearch) {
|
||||
ReferencesSearch.search(classToSearch).forEach(Processor { reference ->
|
||||
if (!processDataClassUsage(reference)) {
|
||||
//TODO
|
||||
val element = reference.element
|
||||
val document = PsiDocumentManager.getInstance(project).getDocument(element.containingFile)
|
||||
val lineAndCol = DiagnosticUtils.offsetToLineAndColumn(document, element.startOffset)
|
||||
TODO("Unsupported reference: '${element.text}' in ${element.containingFile.name} line ${lineAndCol.line} column ${lineAndCol.column}")
|
||||
}
|
||||
else {
|
||||
true
|
||||
ReferencesSearch.search(classToSearch).forEach(Processor processor@ { reference -> //TODO: see KT-13607
|
||||
if (processDataClassUsage(reference)) return@processor true
|
||||
|
||||
if (destructuringDeclarationUsageSearchMode != ALWAYS_SMART) {
|
||||
plainSearchHandler(searchScope)
|
||||
return@processor false
|
||||
}
|
||||
|
||||
val element = reference.element
|
||||
val document = PsiDocumentManager.getInstance(project).getDocument(element.containingFile)
|
||||
val lineAndCol = DiagnosticUtils.offsetToLineAndColumn(document, element.startOffset)
|
||||
error("Unsupported reference: '${element.text}' in ${element.containingFile.name} line ${lineAndCol.line} column ${lineAndCol.column}")
|
||||
})
|
||||
|
||||
// we must use plain search inside our data class (and inheritors) because implicit 'this' can happen anywhere
|
||||
|
||||
@@ -139,6 +139,11 @@
|
||||
<add-to-group group-id="KotlinToolsGroup" anchor="last"/>
|
||||
</action>
|
||||
|
||||
<action id="CheckComponentsUsageSearchAction" class="org.jetbrains.kotlin.idea.actions.internal.CheckComponentsUsageSearchAction"
|
||||
text="Check Component Functions Usage Search">
|
||||
<add-to-group group-id="KotlinToolsGroup" anchor="last"/>
|
||||
</action>
|
||||
|
||||
<action id="KotlinInternalMode" class="org.jetbrains.kotlin.idea.actions.internal.KotlinInternalModeToggleAction">
|
||||
<add-to-group group-id="KotlinToolsGroup" anchor="last"/>
|
||||
</action>
|
||||
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* 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.kotlin.idea.actions.internal
|
||||
|
||||
import com.intellij.openapi.actionSystem.AnAction
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.actionSystem.CommonDataKeys
|
||||
import com.intellij.openapi.progress.EmptyProgressIndicator
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.Messages
|
||||
import com.intellij.openapi.vfs.VfsUtilCore
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.openapi.vfs.VirtualFileVisitor
|
||||
import com.intellij.psi.PsiManager
|
||||
import com.intellij.psi.search.searches.ReferencesSearch
|
||||
import org.jetbrains.kotlin.idea.search.usagesSearch.DestructuringDeclarationUsageSearch
|
||||
import org.jetbrains.kotlin.idea.search.usagesSearch.destructuringDeclarationUsageSearchMode
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.psi.KtClass
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import java.util.*
|
||||
import javax.swing.SwingUtilities
|
||||
|
||||
class CheckComponentsUsageSearchAction : AnAction() {
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
val selectedFiles = selectedKotlinFiles(e).toList()
|
||||
val project = CommonDataKeys.PROJECT.getData(e.dataContext)!!
|
||||
|
||||
ProgressManager.getInstance().runProcessWithProgressSynchronously(
|
||||
{
|
||||
runReadAction { process(selectedFiles, project) }
|
||||
},
|
||||
"Checking Data Classes",
|
||||
true,
|
||||
project)
|
||||
}
|
||||
|
||||
private fun process(files: Collection<KtFile>, project: Project) {
|
||||
val dataClasses = files.asSequence()
|
||||
.flatMap { it.declarations.asSequence() }
|
||||
.filterIsInstance<KtClass>()
|
||||
.filter { it.isData() }
|
||||
.toList()
|
||||
|
||||
val progressIndicator = ProgressManager.getInstance().progressIndicator
|
||||
for ((i, dataClass) in dataClasses.withIndex()) {
|
||||
progressIndicator?.text = "Checking data class ${i + 1} of ${dataClasses.size}..."
|
||||
progressIndicator?.text2 = dataClass.fqName?.asString() ?: ""
|
||||
|
||||
val parameter = dataClass.getPrimaryConstructor()?.valueParameters?.firstOrNull()
|
||||
if (parameter != null) {
|
||||
try {
|
||||
var smartRefsCount = 0
|
||||
var goldRefsCount = 0
|
||||
ProgressManager.getInstance().runProcess(Runnable {
|
||||
destructuringDeclarationUsageSearchMode = DestructuringDeclarationUsageSearch.ALWAYS_SMART
|
||||
|
||||
smartRefsCount = ReferencesSearch.search(parameter).findAll().size
|
||||
|
||||
destructuringDeclarationUsageSearchMode = DestructuringDeclarationUsageSearch.ALWAYS_PLAIN
|
||||
|
||||
goldRefsCount = ReferencesSearch.search(parameter).findAll().size
|
||||
}, EmptyProgressIndicator())
|
||||
|
||||
if (smartRefsCount != goldRefsCount) {
|
||||
SwingUtilities.invokeLater {
|
||||
Messages.showInfoMessage(project, "Difference found for data class ${dataClass.fqName?.asString()}. Found $smartRefsCount usage(s) but $goldRefsCount expected", "Error")
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
finally {
|
||||
destructuringDeclarationUsageSearchMode = DestructuringDeclarationUsageSearch.PLAIN_WHEN_NEEDED
|
||||
}
|
||||
}
|
||||
|
||||
progressIndicator?.fraction = (i + 1) / dataClasses.size.toDouble()
|
||||
}
|
||||
|
||||
SwingUtilities.invokeLater {
|
||||
Messages.showInfoMessage(project, "Analyzed ${dataClasses.size} classes. No difference found.", "Success")
|
||||
}
|
||||
}
|
||||
|
||||
override fun update(e: AnActionEvent) {
|
||||
if (!KotlinInternalMode.enabled) {
|
||||
e.presentation.isVisible = false
|
||||
e.presentation.isEnabled = false
|
||||
}
|
||||
else {
|
||||
e.presentation.isVisible = true
|
||||
e.presentation.isEnabled = selectedKotlinFiles(e).any()
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectedKotlinFiles(e: AnActionEvent): Sequence<KtFile> {
|
||||
val virtualFiles = e.getData(CommonDataKeys.VIRTUAL_FILE_ARRAY) ?: return sequenceOf()
|
||||
val project = CommonDataKeys.PROJECT.getData(e.dataContext) ?: return sequenceOf()
|
||||
return allKotlinFiles(virtualFiles, project)
|
||||
}
|
||||
|
||||
private fun allKotlinFiles(filesOrDirs: Array<VirtualFile>, project: Project): Sequence<KtFile> {
|
||||
val manager = PsiManager.getInstance(project)
|
||||
return allFiles(filesOrDirs)
|
||||
.asSequence()
|
||||
.mapNotNull { manager.findFile(it) as? KtFile }
|
||||
}
|
||||
|
||||
private fun allFiles(filesOrDirs: Array<VirtualFile>): Collection<VirtualFile> {
|
||||
val result = ArrayList<VirtualFile>()
|
||||
for (file in filesOrDirs) {
|
||||
VfsUtilCore.visitChildrenRecursively(file, object : VirtualFileVisitor<Unit>() {
|
||||
override fun visitFile(file: VirtualFile): Boolean {
|
||||
result.add(file)
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,8 @@ import com.intellij.psi.search.LocalSearchScope
|
||||
import com.intellij.psi.search.searches.ReferencesSearch
|
||||
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
|
||||
import org.jetbrains.kotlin.idea.references.KtDestructuringDeclarationReference
|
||||
import org.jetbrains.kotlin.idea.search.usagesSearch.DestructuringDeclarationUsageSearch
|
||||
import org.jetbrains.kotlin.idea.search.usagesSearch.destructuringDeclarationUsageSearchMode
|
||||
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
|
||||
import org.jetbrains.kotlin.psi.KtFunction
|
||||
import org.jetbrains.kotlin.psi.KtParameter
|
||||
@@ -65,9 +67,15 @@ class KotlinReferencesSearchTest(): AbstractSearcherTest() {
|
||||
val func = myFixtureProxy.elementAtCaret.getParentOfType<T>(false)!!
|
||||
val refs = ReferencesSearch.search(func).findAll().sortedBy { it.element.textRange.startOffset }
|
||||
|
||||
// check that local references search gives the same result
|
||||
val localRefs = ReferencesSearch.search(func, LocalSearchScope(psiFile)).findAll()
|
||||
Assert.assertEquals(refs.size, localRefs.size)
|
||||
// check that local reference search gives the same result
|
||||
try {
|
||||
destructuringDeclarationUsageSearchMode = DestructuringDeclarationUsageSearch.PLAIN_WHEN_NEEDED
|
||||
val localRefs = ReferencesSearch.search(func, LocalSearchScope(psiFile)).findAll()
|
||||
Assert.assertEquals(refs.size, localRefs.size)
|
||||
}
|
||||
finally {
|
||||
destructuringDeclarationUsageSearchMode = DestructuringDeclarationUsageSearch.ALWAYS_SMART
|
||||
}
|
||||
|
||||
return refs
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user