Imports on paste: don't try to import symbols not visible from current module

This commit is contained in:
Valentin Kipyatkov
2015-04-08 21:27:06 +03:00
parent ea7732883e
commit 4a3592be50
9 changed files with 113 additions and 41 deletions
@@ -164,32 +164,13 @@ public class KotlinCopyPasteReferenceProcessor() : CopyPastePostProcessor<Kotlin
val fqName = descriptor.importableFqName ?: continue
if (!descriptor.canBeReferencedViaImport()) continue
val kind = referenceDataKind(descriptor) ?: continue
val kind = KotlinReferenceData.Kind.fromDescriptor(descriptor) ?: continue
add(KotlinReferenceData(element.range.start - startOffset, element.range.end - startOffset, fqName.asString(), kind))
}
}
})
}
private fun referenceDataKind(descriptor: DeclarationDescriptor): KotlinReferenceData.Kind? {
return when (descriptor.getImportableDescriptor()) {
is ClassDescriptor ->
KotlinReferenceData.Kind.CLASS
is PackageViewDescriptor ->
KotlinReferenceData.Kind.PACKAGE
is FunctionDescriptor ->
if (descriptor.isExtension) KotlinReferenceData.Kind.EXTENSION_FUNCTION else KotlinReferenceData.Kind.NON_EXTENSION_CALLABLE
is PropertyDescriptor ->
if (descriptor.isExtension) KotlinReferenceData.Kind.EXTENSION_PROPERTY else KotlinReferenceData.Kind.NON_EXTENSION_CALLABLE
else ->
null
}
}
private data class ReferenceToRestoreData(
val reference: JetReference,
val refData: KotlinReferenceData
@@ -234,7 +215,7 @@ public class KotlinCopyPasteReferenceProcessor() : CopyPastePostProcessor<Kotlin
return referenceData.map {
val referenceElement = findReference(it, file, blockStart)
if (referenceElement != null)
createReferenceToRestoreData(referenceElement, it, fileResolutionScope)
createReferenceToRestoreData(referenceElement, it, file, fileResolutionScope)
else
null
}.filterNotNull()
@@ -261,7 +242,7 @@ public class KotlinCopyPasteReferenceProcessor() : CopyPastePostProcessor<Kotlin
return null
}
private fun createReferenceToRestoreData(element: JetElement, refData: KotlinReferenceData, fileResolutionScope: JetScope): ReferenceToRestoreData? {
private fun createReferenceToRestoreData(element: JetElement, refData: KotlinReferenceData, file: JetFile, fileResolutionScope: JetScope): ReferenceToRestoreData? {
val originalFqName = FqName(refData.fqName)
if (refData.kind == KotlinReferenceData.Kind.EXTENSION_FUNCTION) {
@@ -285,6 +266,12 @@ public class KotlinCopyPasteReferenceProcessor() : CopyPastePostProcessor<Kotlin
.filterNotNull()
.toSet()
if (referencedFqNames.singleOrNull() == originalFqName) return null
// check that descriptor to import exists and is accessible from the current module
if (!findImportableDescriptors(originalFqName, file).any { KotlinReferenceData.Kind.fromDescriptor(it) == refData.kind }) {
return null
}
return ReferenceToRestoreData(reference, refData)
}
@@ -302,12 +289,12 @@ public class KotlinCopyPasteReferenceProcessor() : CopyPastePostProcessor<Kotlin
for ((reference, refData) in referencesToRestore) {
val fqName = FqName(refData.fqName)
if (!refData.kind.isExtensionCallable() && reference is JetSimpleNameReference) {
if (!refData.kind.isExtension() && reference is JetSimpleNameReference) {
val pointer = smartPointerManager.createSmartPsiElementPointer(reference.getElement(), file)
bindingRequests.add(BindingRequest(pointer, fqName))
}
if (refData.kind.isExtensionCallable()) {
if (refData.kind.isExtension()) {
extensionsToImport.addIfNotNull(findCallableToImport(fqName, file))
}
}
@@ -322,19 +309,21 @@ public class KotlinCopyPasteReferenceProcessor() : CopyPastePostProcessor<Kotlin
performDelayedShortening(file.getProject())
}
private fun KotlinReferenceData.Kind.isExtensionCallable()
private fun KotlinReferenceData.Kind.isExtension()
= this == KotlinReferenceData.Kind.EXTENSION_FUNCTION || this == KotlinReferenceData.Kind.EXTENSION_PROPERTY
private fun findCallableToImport(fqName: FqName, file: JetFile): CallableDescriptor? {
private fun findImportableDescriptors(fqName: FqName, file: JetFile): Collection<DeclarationDescriptor> {
val importDirective = JetPsiFactory(file.getProject()).createImportDirective(ImportPath(fqName, false))
val moduleDescriptor = file.getResolutionFacade().findModuleDescriptor(file)
val scope = JetModuleUtil.getSubpackagesOfRootScope(moduleDescriptor)
return QualifiedExpressionResolver()
.processImportReference(importDirective, scope, scope, BindingTraceContext(), LookupMode.EVERYTHING)
.getAllDescriptors()
.firstIsInstanceOrNull<CallableDescriptor>()
}
private fun findCallableToImport(fqName: FqName, file: JetFile): CallableDescriptor?
= findImportableDescriptors(fqName, file).firstIsInstanceOrNull<CallableDescriptor>()
private fun showRestoreReferencesDialog(project: Project, referencesToRestore: List<ReferenceToRestoreData>): Collection<ReferenceToRestoreData> {
val fqNames = referencesToRestore.map { it.refData.fqName }.toSortedSet()
@@ -16,8 +16,10 @@
package org.jetbrains.kotlin.idea.codeInsight
import com.intellij.codeInsight.editorActions.ReferenceData
import com.intellij.codeInsight.editorActions.TextBlockTransferableData
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.resolve.descriptorUtil.getImportableDescriptor
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
import java.awt.datatransfer.DataFlavor
import java.io.Serializable
import kotlin.properties.Delegates
@@ -64,6 +66,27 @@ public class KotlinReferenceData(
NON_EXTENSION_CALLABLE
EXTENSION_FUNCTION
EXTENSION_PROPERTY
companion object {
public fun fromDescriptor(descriptor: DeclarationDescriptor): KotlinReferenceData.Kind? {
return when (descriptor.getImportableDescriptor()) {
is ClassDescriptor ->
KotlinReferenceData.Kind.CLASS
is PackageViewDescriptor ->
KotlinReferenceData.Kind.PACKAGE
is FunctionDescriptor ->
if (descriptor.isExtension) KotlinReferenceData.Kind.EXTENSION_FUNCTION else KotlinReferenceData.Kind.NON_EXTENSION_CALLABLE
is PropertyDescriptor ->
if (descriptor.isExtension) KotlinReferenceData.Kind.EXTENSION_PROPERTY else KotlinReferenceData.Kind.NON_EXTENSION_CALLABLE
else ->
null
}
}
}
}
public override fun clone(): KotlinReferenceData {
@@ -1 +0,0 @@
A
@@ -0,0 +1,9 @@
package pack.dependency
class Dependency
fun topLevelFun(){}
fun String.extensionFun(){}
fun String.extensionProp: Int = 1
@@ -0,0 +1,14 @@
// ERROR: Unresolved reference: Dependency
// ERROR: Unresolved reference: topLevelFun
// ERROR: Unresolved reference: extensionFun
// ERROR: Unresolved reference: extensionProp
// ERROR: Unresolved reference: dependency
package to
fun foo(d: Dependency) {
topLevelFun()
"".extensionFun()
println("".extensionProp)
dependency.Dependency()
}
@@ -0,0 +1,14 @@
// RUNTIME
// DELETE_DEPENDENCIES_BEFORE_PASTE
import pack.dependency.*
import pack.dependency
<selection>
fun foo(d: Dependency) {
topLevelFun()
"".extensionFun()
println("".extensionProp)
dependency.Dependency()
}
</selection>
@@ -18,9 +18,11 @@ package org.jetbrains.kotlin.idea.codeInsight
import com.intellij.openapi.actionSystem.IdeActions
import com.intellij.openapi.util.io.FileUtil
import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.idea.AbstractCopyPasteTest
import org.jetbrains.kotlin.idea.PluginTestCaseBase
import org.jetbrains.kotlin.idea.testUtils.dumpTextWithErrors
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.jetbrains.kotlin.test.JetTestUtils
@@ -29,7 +31,9 @@ import java.io.File
public abstract class AbstractInsertImportOnPasteTest : AbstractCopyPasteTest() {
private val BASE_PATH = PluginTestCaseBase.getTestDataPathBase() + "/copyPaste/imports"
private val DEFAULT_TO_FILE_TEXT = "package to\n\n<caret>"
private val NO_ERRORS_DUMP_DIRECTIVE = "// NO_ERRORS_DUMP"
private val DELETE_DEPENDENCIES_BEFORE_PASTE_DIRECTIVE = "// DELETE_DEPENDENCIES_BEFORE_PASTE"
override fun getTestDataPath() = BASE_PATH
@@ -44,13 +48,22 @@ public abstract class AbstractInsertImportOnPasteTest : AbstractCopyPasteTest()
private fun doTestAction(cutOrCopy: String, path: String) {
myFixture.setTestDataPath(BASE_PATH)
val testFile = File(path)
val testFileText = FileUtil.loadFile(testFile, true)
val testFileName = testFile.getName()
configureByDependencyIfExists(testFileName.replace(".kt", ".dependency.kt"))
configureByDependencyIfExists(testFileName.replace(".kt", ".dependency.java"))
val dependencyPsiFile1 = configureByDependencyIfExists(testFileName.replace(".kt", ".dependency.kt"))
val dependencyPsiFile2 = configureByDependencyIfExists(testFileName.replace(".kt", ".dependency.java"))
myFixture.configureByFile(testFileName)
myFixture.performEditorAction(cutOrCopy)
if (InTextDirectivesUtils.isDirectiveDefined(testFileText, DELETE_DEPENDENCIES_BEFORE_PASTE_DIRECTIVE)) {
assert(dependencyPsiFile1 != null || dependencyPsiFile2 != null)
runWriteAction {
dependencyPsiFile1?.getVirtualFile()?.delete(null)
dependencyPsiFile2?.getVirtualFile()?.delete(null)
}
}
KotlinCopyPasteReferenceProcessor.declarationsToImportSuggested = emptyList()
configureToFile(testFileName.replace(".kt", ".to.kt"))
@@ -60,7 +73,7 @@ public abstract class AbstractInsertImportOnPasteTest : AbstractCopyPasteTest()
JetTestUtils.assertEqualsToFile(File(path.replace(".kt", ".expected.names")), namesToImportDump)
val resultFile = myFixture.getFile() as JetFile
val resultText = if (InTextDirectivesUtils.isDirectiveDefined(FileUtil.loadFile(testFile, true), NO_ERRORS_DUMP_DIRECTIVE))
val resultText = if (InTextDirectivesUtils.isDirectiveDefined(testFileText, NO_ERRORS_DUMP_DIRECTIVE))
resultFile.getText()
else
resultFile.dumpTextWithErrors()
@@ -76,16 +89,15 @@ public abstract class AbstractInsertImportOnPasteTest : AbstractCopyPasteTest()
}
}
private fun configureByDependencyIfExists(dependencyFileName: String) {
private fun configureByDependencyIfExists(dependencyFileName: String): PsiFile? {
val file = File(BASE_PATH + "/" + dependencyFileName)
if (file.exists()) {
if (dependencyFileName.endsWith(".java")) {
//allow test framework to put it under right directory
myFixture.addClass(FileUtil.loadFile(file, true))
}
else {
myFixture.configureByFile(dependencyFileName)
}
if (!file.exists()) return null
return if (dependencyFileName.endsWith(".java")) {
//allow test framework to put it under right directory
myFixture.addClass(FileUtil.loadFile(file, true)).getContainingFile()
}
else {
myFixture.configureByFile(dependencyFileName)
}
}
}
@@ -132,6 +132,12 @@ public class InsertImportOnPasteTestGenerated extends AbstractInsertImportOnPast
doTestCopy(fileName);
}
@TestMetadata("DependenciesNotAccessibleOnPaste.kt")
public void testDependenciesNotAccessibleOnPaste() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/copyPaste/imports/DependenciesNotAccessibleOnPaste.kt");
doTestCopy(fileName);
}
@TestMetadata("DependencyOnJava.kt")
public void testDependencyOnJava() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/copyPaste/imports/DependencyOnJava.kt");
@@ -465,6 +471,12 @@ public class InsertImportOnPasteTestGenerated extends AbstractInsertImportOnPast
doTestCut(fileName);
}
@TestMetadata("DependenciesNotAccessibleOnPaste.kt")
public void testDependenciesNotAccessibleOnPaste() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/copyPaste/imports/DependenciesNotAccessibleOnPaste.kt");
doTestCut(fileName);
}
@TestMetadata("DependencyOnJava.kt")
public void testDependencyOnJava() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/copyPaste/imports/DependencyOnJava.kt");