diff --git a/idea/testData/multiModuleFindUsages/findImplFromHeader/common/common.kt b/idea/testData/multiModuleFindUsages/findImplFromHeader/common/common.kt new file mode 100644 index 00000000000..ceb93323090 --- /dev/null +++ b/idea/testData/multiModuleFindUsages/findImplFromHeader/common/common.kt @@ -0,0 +1,4 @@ +// PSI_ELEMENT: org.jetbrains.kotlin.psi.KtNamedFunction +// OPTIONS: usages + +header fun boo(s: String) \ No newline at end of file diff --git a/idea/testData/multiModuleFindUsages/findImplFromHeader/common/common.results.txt b/idea/testData/multiModuleFindUsages/findImplFromHeader/common/common.results.txt new file mode 100644 index 00000000000..c5028eb897b --- /dev/null +++ b/idea/testData/multiModuleFindUsages/findImplFromHeader/common/common.results.txt @@ -0,0 +1,2 @@ +[jvm.kt] Function call 4 boo("a") +[jvm.kt] Function call 5 boo("b") \ No newline at end of file diff --git a/idea/testData/multiModuleFindUsages/findImplFromHeader/jvm/jvm.kt b/idea/testData/multiModuleFindUsages/findImplFromHeader/jvm/jvm.kt new file mode 100644 index 00000000000..bf20ed386ea --- /dev/null +++ b/idea/testData/multiModuleFindUsages/findImplFromHeader/jvm/jvm.kt @@ -0,0 +1,6 @@ +impl fun boo(s: String) {} + +fun test() { + boo("a") + boo("b") +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/findUsages/AbstractFindUsagesMultiModuleTest.kt b/idea/tests/org/jetbrains/kotlin/findUsages/AbstractFindUsagesMultiModuleTest.kt new file mode 100644 index 00000000000..d0be08076b8 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/findUsages/AbstractFindUsagesMultiModuleTest.kt @@ -0,0 +1,60 @@ +/* + * Copyright 2010-2017 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.findUsages + +import com.intellij.codeInsight.TargetElementUtil +import com.intellij.testFramework.UsefulTestCase +import org.jetbrains.kotlin.idea.project.PluginJetFilesProvider +import org.jetbrains.kotlin.idea.stubs.AbstractMultiModuleTest +import org.jetbrains.kotlin.idea.test.PluginTestCaseBase +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.test.InTextDirectivesUtils + +abstract class AbstractFindUsagesMultiModuleTest : AbstractMultiModuleTest() { + + override val testPath = PluginTestCaseBase.getTestDataPathBase() + "/multiModuleFindUsages/" + + protected fun doFindUsagesTest() { + val allFilesInProject = PluginJetFilesProvider.allFilesInProject(myProject!!) + val mainFile = allFilesInProject.single { file -> + file.text.contains("// ") + } + + + val virtualFile = mainFile.virtualFile!! + configureByExistingFile(virtualFile) + + val mainFileName = mainFile.name + val mainFileText = mainFile.text + val prefix = mainFileName.substringBefore(".") + "." + + val caretElementClassNames = InTextDirectivesUtils.findLinesWithPrefixesRemoved(mainFileText, "// PSI_ELEMENT: ") + + @Suppress("UNCHECKED_CAST") + val caretElementClass = Class.forName(caretElementClassNames.single()) as Class + + val parser = OptionsParser.getParserByPsiElementClass(caretElementClass) + + val rootPath = virtualFile.path.substringBeforeLast("/") + "/" + + val caretElement = TargetElementUtil.findTargetElement(myEditor, TargetElementUtil.ELEMENT_NAME_ACCEPTED) + UsefulTestCase.assertInstanceOf(caretElement!!, caretElementClass) + + val options = parser?.parse(mainFileText, project) + findUsagesAndCheckResults(mainFileText, prefix, rootPath, caretElement, options, project, alwaysAppendFileName = true) + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/findUsages/AbstractFindUsagesTest.kt b/idea/tests/org/jetbrains/kotlin/findUsages/AbstractFindUsagesTest.kt index 0df300a3442..76ec73db93d 100644 --- a/idea/tests/org/jetbrains/kotlin/findUsages/AbstractFindUsagesTest.kt +++ b/idea/tests/org/jetbrains/kotlin/findUsages/AbstractFindUsagesTest.kt @@ -17,7 +17,7 @@ package org.jetbrains.kotlin.findUsages import com.intellij.codeInsight.JavaTargetElementEvaluator -import com.intellij.codeInsight.TargetElementUtilBase +import com.intellij.codeInsight.TargetElementUtil import com.intellij.find.FindManager import com.intellij.find.findUsages.FindUsagesHandler import com.intellij.find.findUsages.FindUsagesOptions @@ -28,6 +28,7 @@ import com.intellij.lang.properties.psi.PropertiesFile import com.intellij.lang.properties.psi.Property import com.intellij.openapi.editor.markup.TextAttributes import com.intellij.openapi.extensions.Extensions +import com.intellij.openapi.project.Project import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.util.io.FileUtilRt import com.intellij.psi.PsiComment @@ -49,7 +50,6 @@ import org.jetbrains.kotlin.idea.findUsages.KotlinFindUsagesHandlerFactory import org.jetbrains.kotlin.idea.search.usagesSearch.ExpressionsOfTypeProcessor import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor -import org.jetbrains.kotlin.idea.test.PluginTestCaseBase import org.jetbrains.kotlin.idea.test.TestFixtureExtension import org.jetbrains.kotlin.idea.util.ProjectRootsUtil import org.jetbrains.kotlin.psi.KtDeclaration @@ -114,8 +114,8 @@ abstract class AbstractFindUsagesTest : KotlinLightCodeInsightFixtureTestCase() myFixture.configureByFile(mainFileName) val caretElement = if (InTextDirectivesUtils.isDirectiveDefined(mainFileText, "// FIND_BY_REF")) - TargetElementUtilBase.findTargetElement(myFixture.editor, - TargetElementUtilBase.REFERENCED_ELEMENT_ACCEPTED or JavaTargetElementEvaluator.NEW_AS_CONSTRUCTOR)!! + TargetElementUtil.findTargetElement(myFixture.editor, + TargetElementUtil.REFERENCED_ELEMENT_ACCEPTED or JavaTargetElementEvaluator.NEW_AS_CONSTRUCTOR)!! else myFixture.elementAtCaret UsefulTestCase.assertInstanceOf(caretElement, caretElementClass) @@ -128,15 +128,15 @@ abstract class AbstractFindUsagesTest : KotlinLightCodeInsightFixtureTestCase() // Ensure that search by sources (if present) and decompiled declarations gives the same results if (isLibraryElement) { val originalElement = caretElement.originalElement - findUsagesAndCheckResults(mainFileText, prefix, rootPath, originalElement, options) + findUsagesAndCheckResults(mainFileText, prefix, rootPath, originalElement, options, project) val navigationElement = caretElement.navigationElement if (navigationElement !== originalElement) { - findUsagesAndCheckResults(mainFileText, prefix, rootPath, navigationElement, options) + findUsagesAndCheckResults(mainFileText, prefix, rootPath, navigationElement, options, project) } } else { - findUsagesAndCheckResults(mainFileText, prefix, rootPath, caretElement, options) + findUsagesAndCheckResults(mainFileText, prefix, rootPath, caretElement, options, project) } } finally { @@ -144,138 +144,13 @@ abstract class AbstractFindUsagesTest : KotlinLightCodeInsightFixtureTestCase() } } - private fun findUsagesAndCheckResults( - mainFileText: String, - prefix: String, - rootPath: String, - caretElement: T, - options: FindUsagesOptions? - ) { - val highlightingMode = InTextDirectivesUtils.isDirectiveDefined(mainFileText, "// HIGHLIGHTING") - - var log: String? = null - val logList = ArrayList() - val usageInfos = try { - if (ExpressionsOfTypeProcessor.mode !== ExpressionsOfTypeProcessor.Mode.ALWAYS_PLAIN) { - ExpressionsOfTypeProcessor.testLog = logList - } - - if (InTextDirectivesUtils.isDirectiveDefined(mainFileText, "// PLAIN_WHEN_NEEDED")) { - ExpressionsOfTypeProcessor.mode = ExpressionsOfTypeProcessor.Mode.PLAIN_WHEN_NEEDED - } - - findUsages(caretElement, options, highlightingMode) - } - finally { - ExpressionsOfTypeProcessor.testLog = null - if (logList.size > 0) { - log = logList.sorted().joinToString("\n") - } - - ExpressionsOfTypeProcessor.mode = ExpressionsOfTypeProcessor.Mode.ALWAYS_SMART - } - - val filteringRules = instantiateClasses(mainFileText, "// FILTERING_RULES: ") - val groupingRules = instantiateClasses(mainFileText, "// GROUPING_RULES: ") - - val filteredUsages = getUsageAdapters(filteringRules, usageInfos) - - val usageFiles = filteredUsages.map { it.file.name }.distinct() - val appendFileName = usageFiles.size > 1 - - val convertToString: (UsageInfo2UsageAdapter) -> String = { usageAdapter -> - var groupAsString = groupingRules - .map { it.groupUsage(usageAdapter)?.getText(null) ?: "" } - .joinToString(", ") - if (!groupAsString.isEmpty()) { - groupAsString = "($groupAsString) " - } - - val usageType = getUsageType(usageAdapter.element) - val usageTypeAsString = usageType?.toString(USAGE_VIEW_PRESENTATION) ?: "null" - - val usageChunks = ArrayList() - usageChunks.addAll(usageAdapter.presentation.text.asList()) - usageChunks.add(1, TextChunk(TextAttributes(), " ")) // add space after line number - - buildString { - if (appendFileName) { - append("[").append(usageAdapter.file.name).append("] ") - } - append(usageTypeAsString) - append(" ") - append(groupAsString) - append(usageChunks.joinToString("")) - } - } - - val finalUsages = filteredUsages.map(convertToString).sorted() - KotlinTestUtils.assertEqualsToFile(File(rootPath, prefix + "results.txt"), finalUsages.joinToString("\n")) - - if (log != null) { - KotlinTestUtils.assertEqualsToFile(File(rootPath, prefix + "log"), log) - - // if log is empty then compare results with plain search - try { - ExpressionsOfTypeProcessor.mode = ExpressionsOfTypeProcessor.Mode.ALWAYS_PLAIN - - findUsagesAndCheckResults(mainFileText, prefix, rootPath, caretElement, options) - } - finally { - ExpressionsOfTypeProcessor.mode = ExpressionsOfTypeProcessor.Mode.ALWAYS_SMART - } - } - } - - protected fun findUsages( - targetElement: PsiElement, - options: FindUsagesOptions?, - highlightingMode: Boolean - ): Collection { - val project = project - - val handler: FindUsagesHandler = (if (targetElement is PsiMember) { - JavaFindUsagesHandler(targetElement, JavaFindUsagesHandlerFactory(project)) - } - else if (targetElement is KtDeclaration && targetElement !is KtTypeAlias) { - KotlinFindUsagesHandlerFactory(project).createFindUsagesHandlerNoQuestions(targetElement) - } - else { - (FindManager.getInstance(project) as FindManagerImpl).findUsagesManager.getFindUsagesHandler(targetElement, false) - }) ?: error("Cannot find handler for: $targetElement") - - @Suppress("NAME_SHADOWING") - val options = options ?: handler.getFindUsagesOptions(null) - - options.searchScope = GlobalSearchScope.allScope(project) - - val processor = CommonProcessors.CollectProcessor() - for (psiElement in handler.primaryElements + handler.secondaryElements) { - if (highlightingMode) { - //TODO: should findReferencesToHighlight work outside read-action or it makes no sense? - for (reference in handler.findReferencesToHighlight(psiElement, options.searchScope)) { - processor.process(UsageInfo(reference)) - } - } - else { - // run in another thread to test read-action assertions - val thread = Thread { - handler.processElementUsages(psiElement, processor, options) - } - thread.start() - thread.join() - } - } - - return processor.results - } companion object { val SUPPORTED_EXTENSIONS = setOf("kt", "java", "xml", "properties", "txt", "groovy") val USAGE_VIEW_PRESENTATION = UsageViewPresentation() - private fun getUsageAdapters( + internal fun getUsageAdapters( filters: Collection, usageInfos: Collection ): Collection { @@ -284,7 +159,7 @@ abstract class AbstractFindUsagesTest : KotlinLightCodeInsightFixtureTestCase() .filter { usageAdapter -> filters.all { it.isVisible(usageAdapter) } } } - private fun getUsageType(element: PsiElement?): UsageType? { + internal fun getUsageType(element: PsiElement?): UsageType? { if (element == null) return null if (element.getNonStrictParentOfType() != null) { @@ -298,7 +173,7 @@ abstract class AbstractFindUsagesTest : KotlinLightCodeInsightFixtureTestCase() ?: UsageType.UNCLASSIFIED } - private fun instantiateClasses(mainFileText: String, directive: String): Collection { + internal fun instantiateClasses(mainFileText: String, directive: String): Collection { val filteringRuleClassNames = InTextDirectivesUtils.findLinesWithPrefixesRemoved(mainFileText, directive) return filteringRuleClassNames .map { @@ -308,3 +183,132 @@ abstract class AbstractFindUsagesTest : KotlinLightCodeInsightFixtureTestCase() } } } + +internal fun findUsagesAndCheckResults( + mainFileText: String, + prefix: String, + rootPath: String, + caretElement: T, + options: FindUsagesOptions?, + project: Project, + alwaysAppendFileName: Boolean = false +) { + val highlightingMode = InTextDirectivesUtils.isDirectiveDefined(mainFileText, "// HIGHLIGHTING") + + var log: String? = null + val logList = ArrayList() + val usageInfos = try { + if (ExpressionsOfTypeProcessor.mode !== ExpressionsOfTypeProcessor.Mode.ALWAYS_PLAIN) { + ExpressionsOfTypeProcessor.testLog = logList + } + + if (InTextDirectivesUtils.isDirectiveDefined(mainFileText, "// PLAIN_WHEN_NEEDED")) { + ExpressionsOfTypeProcessor.mode = ExpressionsOfTypeProcessor.Mode.PLAIN_WHEN_NEEDED + } + + findUsages(caretElement, options, highlightingMode, project) + } + finally { + ExpressionsOfTypeProcessor.testLog = null + if (logList.size > 0) { + log = logList.sorted().joinToString("\n") + } + + ExpressionsOfTypeProcessor.mode = ExpressionsOfTypeProcessor.Mode.ALWAYS_SMART + } + + val filteringRules = AbstractFindUsagesTest.instantiateClasses(mainFileText, "// FILTERING_RULES: ") + val groupingRules = AbstractFindUsagesTest.instantiateClasses(mainFileText, "// GROUPING_RULES: ") + + val filteredUsages = AbstractFindUsagesTest.getUsageAdapters(filteringRules, usageInfos) + + val usageFiles = filteredUsages.map { it.file.name }.distinct() + val appendFileName = alwaysAppendFileName || usageFiles.size > 1 + + val convertToString: (UsageInfo2UsageAdapter) -> String = { usageAdapter -> + var groupAsString = groupingRules + .map { it.groupUsage(usageAdapter)?.getText(null) ?: "" } + .joinToString(", ") + if (!groupAsString.isEmpty()) { + groupAsString = "($groupAsString) " + } + + val usageType = AbstractFindUsagesTest.getUsageType(usageAdapter.element) + val usageTypeAsString = usageType?.toString(AbstractFindUsagesTest.USAGE_VIEW_PRESENTATION) ?: "null" + + val usageChunks = ArrayList() + usageChunks.addAll(usageAdapter.presentation.text.asList()) + usageChunks.add(1, TextChunk(TextAttributes(), " ")) // add space after line number + + buildString { + if (appendFileName) { + append("[").append(usageAdapter.file.name).append("] ") + } + append(usageTypeAsString) + append(" ") + append(groupAsString) + append(usageChunks.joinToString("")) + } + } + + val finalUsages = filteredUsages.map(convertToString).sorted() + KotlinTestUtils.assertEqualsToFile(File(rootPath, prefix + "results.txt"), finalUsages.joinToString("\n")) + + if (log != null) { + KotlinTestUtils.assertEqualsToFile(File(rootPath, prefix + "log"), log) + + // if log is empty then compare results with plain search + try { + ExpressionsOfTypeProcessor.mode = ExpressionsOfTypeProcessor.Mode.ALWAYS_PLAIN + + findUsagesAndCheckResults(mainFileText, prefix, rootPath, caretElement, options, project) + } + finally { + ExpressionsOfTypeProcessor.mode = ExpressionsOfTypeProcessor.Mode.ALWAYS_SMART + } + } +} + + +internal fun findUsages( + targetElement: PsiElement, + options: FindUsagesOptions?, + highlightingMode: Boolean, + project: Project +): Collection { + val handler: FindUsagesHandler = (if (targetElement is PsiMember) { + JavaFindUsagesHandler(targetElement, JavaFindUsagesHandlerFactory(project)) + } + else if (targetElement is KtDeclaration && targetElement !is KtTypeAlias) { + KotlinFindUsagesHandlerFactory(project).createFindUsagesHandlerNoQuestions(targetElement) + } + else { + (FindManager.getInstance(project) as FindManagerImpl).findUsagesManager.getFindUsagesHandler(targetElement, false) + }) ?: error("Cannot find handler for: $targetElement") + + @Suppress("NAME_SHADOWING") + val options = options ?: handler.getFindUsagesOptions(null) + + options.searchScope = GlobalSearchScope.allScope(project) + + val processor = CommonProcessors.CollectProcessor() + for (psiElement in handler.primaryElements + handler.secondaryElements) { + if (highlightingMode) { + //TODO: should findReferencesToHighlight work outside read-action or it makes no sense? + for (reference in handler.findReferencesToHighlight(psiElement, options.searchScope)) { + processor.process(UsageInfo(reference)) + } + } + else { + // run in another thread to test read-action assertions + val thread = Thread { + handler.processElementUsages(psiElement, processor, options) + } + thread.start() + thread.join() + } + } + + return processor.results +} + diff --git a/idea/tests/org/jetbrains/kotlin/findUsages/FindUsagesMultiModuleTest.kt b/idea/tests/org/jetbrains/kotlin/findUsages/FindUsagesMultiModuleTest.kt new file mode 100644 index 00000000000..ebea1771b83 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/findUsages/FindUsagesMultiModuleTest.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2010-2017 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.findUsages + +import org.jetbrains.kotlin.config.JvmTarget +import org.jetbrains.kotlin.config.TargetPlatformKind +import org.junit.Test + +class FindUsagesMultiModuleTest : AbstractFindUsagesMultiModuleTest() { + + private fun doMultiPlatformTest(commonName: String = "common", + implName: String = "jvm", + implKind: TargetPlatformKind<*> = TargetPlatformKind.Jvm[JvmTarget.JVM_1_6]) { + val header = module(commonName) + header.createFacet(TargetPlatformKind.Common) + + val jvm = module(implName) + jvm.createFacet(implKind) + jvm.enableMultiPlatform() + jvm.addDependency(header) + + doFindUsagesTest() + } + + @Test + fun testFindImplFromHeader() { + doMultiPlatformTest() + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/findUsages/KotlinFindUsagesWithLibraryCustomTest.kt b/idea/tests/org/jetbrains/kotlin/findUsages/KotlinFindUsagesWithLibraryCustomTest.kt index 1fc23fb9b39..926c8f208a7 100644 --- a/idea/tests/org/jetbrains/kotlin/findUsages/KotlinFindUsagesWithLibraryCustomTest.kt +++ b/idea/tests/org/jetbrains/kotlin/findUsages/KotlinFindUsagesWithLibraryCustomTest.kt @@ -19,14 +19,13 @@ package org.jetbrains.kotlin.findUsages import com.intellij.psi.search.FilenameIndex import org.jetbrains.kotlin.psi.KtParameter import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType -import kotlin.test.assertEquals class KotlinFindUsagesWithLibraryCustomTest : AbstractKotlinFindUsagesWithLibraryTest() { fun testFindUsagesForLocalClassProperty() { val libraryFile = FilenameIndex.getFilesByName(getProject(), "library.kt", myFixture.module.moduleWithLibrariesScope).first() val indexOf = libraryFile.text.indexOf("localClassProperty") val jetParameter = libraryFile.findElementAt(indexOf)!!.getStrictParentOfType()!! - val usages = findUsages(jetParameter.getOriginalElement(), null, false) + val usages = findUsages(jetParameter.getOriginalElement(), null, false, project) assertEquals(2, usages.size) } } \ No newline at end of file