diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/destructuringDeclarationUsages.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/destructuringDeclarationUsages.kt index b19e60d2de8..f1ab44baa94 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/destructuringDeclarationUsages.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/destructuringDeclarationUsages.kt @@ -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, 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 diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index a86e4b357e9..5ed0736741c 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -139,6 +139,11 @@ + + + + diff --git a/idea/src/org/jetbrains/kotlin/idea/actions/internal/CheckComponentsUsageSearchAction.kt b/idea/src/org/jetbrains/kotlin/idea/actions/internal/CheckComponentsUsageSearchAction.kt new file mode 100644 index 00000000000..e8fc815fafd --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/actions/internal/CheckComponentsUsageSearchAction.kt @@ -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, project: Project) { + val dataClasses = files.asSequence() + .flatMap { it.declarations.asSequence() } + .filterIsInstance() + .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 { + 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, project: Project): Sequence { + val manager = PsiManager.getInstance(project) + return allFiles(filesOrDirs) + .asSequence() + .mapNotNull { manager.findFile(it) as? KtFile } + } + + private fun allFiles(filesOrDirs: Array): Collection { + val result = ArrayList() + for (file in filesOrDirs) { + VfsUtilCore.visitChildrenRecursively(file, object : VirtualFileVisitor() { + override fun visitFile(file: VirtualFile): Boolean { + result.add(file) + return true + } + }) + } + return result + } +} diff --git a/idea/tests/org/jetbrains/kotlin/search/KotlinReferencesSearchTest.kt b/idea/tests/org/jetbrains/kotlin/search/KotlinReferencesSearchTest.kt index 9cffed97647..8251c4f3dfe 100644 --- a/idea/tests/org/jetbrains/kotlin/search/KotlinReferencesSearchTest.kt +++ b/idea/tests/org/jetbrains/kotlin/search/KotlinReferencesSearchTest.kt @@ -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(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 }