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:
Valentin Kipyatkov
2016-08-25 22:48:19 +03:00
parent d13cdc67f0
commit ecb1910bf2
4 changed files with 177 additions and 15 deletions
@@ -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
+5
View File
@@ -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>
@@ -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
}