diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/searchUtil.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/searchUtil.kt index ccc098a9fe8..ba6e4859f25 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/searchUtil.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/searchUtil.kt @@ -19,18 +19,27 @@ package org.jetbrains.kotlin.idea.search import com.intellij.openapi.fileTypes.FileType import com.intellij.openapi.fileTypes.FileTypeRegistry import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.FileIndexFacade +import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile +import com.intellij.psi.impl.cache.impl.id.IdIndex +import com.intellij.psi.impl.cache.impl.id.IdIndexEntry import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.LocalSearchScope import com.intellij.psi.search.PsiSearchHelper import com.intellij.psi.search.SearchScope import com.intellij.psi.search.searches.ReferencesSearch +import com.intellij.util.Processor +import com.intellij.util.indexing.FileBasedIndex import org.jetbrains.kotlin.idea.KotlinFileType import org.jetbrains.kotlin.idea.util.compat.psiSearchHelperInstance +import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.script.findScriptDefinition import org.jetbrains.kotlin.types.expressions.OperatorConventions infix fun SearchScope.and(otherScope: SearchScope): SearchScope = intersectWith(otherScope) @@ -108,7 +117,36 @@ fun PsiSearchHelper.isCheapEnoughToSearchConsideringOperators( fileToIgnoreOccurrencesIn: PsiFile?, progress: ProgressIndicator? ): PsiSearchHelper.SearchCostResult { - if (OperatorConventions.isConventionName(Name.identifier(name))) return PsiSearchHelper.SearchCostResult.TOO_MANY_OCCURRENCES + if (OperatorConventions.isConventionName(Name.identifier(name))) { + return PsiSearchHelper.SearchCostResult.TOO_MANY_OCCURRENCES + } + + if (!isCheapToSearchUsagesInScripts(scope.restrictToKotlinSources(), name)) { + return PsiSearchHelper.SearchCostResult.TOO_MANY_OCCURRENCES + } return isCheapEnoughToSearch(name, scope, fileToIgnoreOccurrencesIn, progress) } + +private fun isCheapToSearchUsagesInScripts(scope: GlobalSearchScope, name: String): Boolean { + val project = scope.project ?: return true + + var scriptsCount = 0 + val processor = object : Processor { + override fun process(file: VirtualFile): Boolean { + ProgressManager.checkCanceled() + if (findScriptDefinition(file, project) == null) return true + return scriptsCount++ < 3 + } + } + + val index = FileIndexFacade.getInstance(project) + return runReadAction { + FileBasedIndex.getInstance().processFilesContainingAllKeys( + IdIndex.NAME, + listOf(IdIndexEntry(name, true)), + scope, + null, + { file -> !index.shouldBeFound(scope, file) || processor.process(file) }) + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedSymbolInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedSymbolInspection.kt index af26a031baf..0d40d495d09 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedSymbolInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedSymbolInspection.kt @@ -24,6 +24,7 @@ import com.intellij.psi.PsiElement import com.intellij.psi.PsiElementVisitor import com.intellij.psi.PsiReference import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.search.PsiSearchHelper.SearchCostResult import com.intellij.psi.search.PsiSearchHelper.SearchCostResult.* import com.intellij.psi.search.SearchScope import com.intellij.psi.search.searches.DefinitionsScopedSearch @@ -109,7 +110,34 @@ class UnusedSymbolInspection : AbstractKotlinInspection() { } else -> return false } - return lightElement != null && javaInspection.isEntryPoint(lightElement) + + if (lightElement == null) return false + + if (isCheapEnoughToSearchUsages(declaration) == TOO_MANY_OCCURRENCES) return false + + return javaInspection.isEntryPoint(lightElement) + } + + private fun isCheapEnoughToSearchUsages(declaration: KtNamedDeclaration): SearchCostResult { + val project = declaration.project + val psiSearchHelper = psiSearchHelperInstance(project) + + val useScope = psiSearchHelper.getUseScope(declaration) + if (useScope is GlobalSearchScope) { + var zeroOccurrences = true + for (name in listOf(declaration.name) + declaration.getAccessorNames() + listOfNotNull(declaration.getClassNameForCompanionObject())) { + if (name == null) continue + when (psiSearchHelper.isCheapEnoughToSearchConsideringOperators(name, useScope, null, null)) { + ZERO_OCCURRENCES -> { + } // go on, check other names + FEW_OCCURRENCES -> zeroOccurrences = false + TOO_MANY_OCCURRENCES -> return TOO_MANY_OCCURRENCES // searching usages is too expensive; behave like it is used + } + } + + if (zeroOccurrences) return ZERO_OCCURRENCES + } + return FEW_OCCURRENCES } private fun KtProperty.isSerializationImplicitlyUsedField(): Boolean { @@ -223,18 +251,13 @@ class UnusedSymbolInspection : AbstractKotlinInspection() { val project = declaration.project val psiSearchHelper = psiSearchHelperInstance(project) - val useScope = declaration.useScope + val useScope = psiSearchHelper.getUseScope(declaration) val restrictedScope = if (useScope is GlobalSearchScope) { - var zeroOccurrences = true - - for (name in listOf(declaration.name) + declaration.getAccessorNames() + listOfNotNull(declaration.getClassNameForCompanionObject())) { - if (name == null) continue - when (psiSearchHelper.isCheapEnoughToSearchConsideringOperators(name, useScope, null, null)) { - ZERO_OCCURRENCES -> { - } // go on, check other names - FEW_OCCURRENCES -> zeroOccurrences = false - TOO_MANY_OCCURRENCES -> return true // searching usages is too expensive; behave like it is used - } + val enoughToSearchUsages = isCheapEnoughToSearchUsages(declaration) + val zeroOccurrences = when (enoughToSearchUsages) { + ZERO_OCCURRENCES -> true + FEW_OCCURRENCES -> false + TOO_MANY_OCCURRENCES -> return true // searching usages is too expensive; behave like it is used } if (zeroOccurrences && !declaration.hasActualModifier()) {