Import insertion algorithm

This commit is contained in:
Valentin Kipyatkov
2015-01-22 23:04:32 +03:00
parent 975b67ed29
commit dfe71ef7ae
83 changed files with 1033 additions and 73 deletions
@@ -141,6 +141,7 @@ import org.jetbrains.kotlin.idea.decompiler.stubBuilder.AbstractClsStubBuilderTe
import org.jetbrains.kotlin.codegen.AbstractLineNumberTest
import org.jetbrains.kotlin.completion.handlers.AbstractKeywordCompletionHandlerTest
import org.jetbrains.kotlin.idea.kdoc.AbstractKDocHighlightingTest
import org.jetbrains.kotlin.addImport.AbstractAddImportTest
fun main(args: Array<String>) {
System.setProperty("java.awt.headless", "true")
@@ -595,6 +596,9 @@ fun main(args: Array<String>) {
testClass(javaClass<AbstractShortenRefsTest>()) {
model("shortenRefs", pattern = """^([^\.]+)\.kt$""")
}
testClass(javaClass<AbstractAddImportTest>()) {
model("addImport", pattern = """^([^\.]+)\.kt$""")
}
testClass(javaClass<AbstractCompiledKotlinInJavaCompletionTest>()) {
model("completion/injava", extension = "java")
@@ -39,6 +39,7 @@ import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.psi.JetDeclaration
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.scopes.JetScope
private val LOG = Logger.getInstance(javaClass<KotlinCacheService>())
@@ -66,6 +67,10 @@ public class KotlinCacheService(val project: Project) {
return cache.getAnalysisResultsForElements(elements)
}
override fun getFileTopLevelScope(file: JetFile): JetScope {
return getLazyResolveSession(file).getScopeProvider().getFileScope(file)
}
override fun <T> get(extension: CacheExtension<T>): T {
return cache[extension]
}
@@ -23,6 +23,8 @@ import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.psi.JetDeclaration
import org.jetbrains.kotlin.analyzer.AnalysisResult
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.resolve.scopes.JetScope
public trait ResolutionFacade {
@@ -32,6 +34,8 @@ public trait ResolutionFacade {
public fun resolveToDescriptor(declaration: JetDeclaration): DeclarationDescriptor
public fun getFileTopLevelScope(file: JetFile): JetScope
public fun findModuleDescriptor(element: JetElement): ModuleDescriptor
public fun <T> get(extension: CacheExtension<T>): T
@@ -27,16 +27,15 @@ import com.intellij.psi.util.PsiTreeUtil
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiDocumentManager
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.resolve.DescriptorUtils
import java.util.Collections
import org.jetbrains.kotlin.analyzer.analyzeInContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getCalleeExpressionIfAny
import java.util.LinkedHashSet
import org.jetbrains.kotlin.resolve.ImportPath
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedElement
import org.jetbrains.kotlin.resolve.descriptorUtil.getImportableDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionFacade
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.resolve.DescriptorUtils
public object ShortenReferences {
public fun process(element: JetElement) {
@@ -303,17 +302,9 @@ public object ShortenReferences {
private var optimizeImports = true
fun addImport(target: DeclarationDescriptor): Boolean {
val realTarget = if (DescriptorUtils.isClassObject(target)) // references to class object are treated as ones to its owner class
target.getContainingDeclaration() as? ClassDescriptor ?: return false
else
target
if (realTarget !is ClassDescriptor && realTarget !is PackageViewDescriptor) return false
if (realTarget.getContainingDeclaration() is ClassDescriptor) return false // do not insert imports for nested classes
if (target !is ClassDescriptor && target !is PackageViewDescriptor) return false
optimizeImports()
ImportInsertHelper.INSTANCE.writeImportToFile(ImportPath(DescriptorUtils.getFqNameSafe(realTarget), false), file)
return true
return ImportInsertHelper.INSTANCE.importDescriptor(file, target)
}
fun optimizeImports(): Boolean {
@@ -21,6 +21,8 @@ import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.psi.JetImportDirective
import org.jetbrains.kotlin.resolve.ImportPath
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionFacade
public trait ImportInsertHelper {
@@ -34,7 +36,13 @@ public trait ImportInsertHelper {
public fun needImport(importPath: ImportPath, file: JetFile, importDirectives: List<JetImportDirective> = file.getImportDirectives()): Boolean
public fun writeImportToFile(importPath: ImportPath, file: JetFile)
public fun writeImportToFile(importPath: ImportPath, file: JetFile): JetImportDirective
/**
* Returns true, if the descriptor is imported (even if no import was added because it's not needed)
* and false, if importing of this descriptor is either impossible or not allowed by code style.
*/
public fun importDescriptor(file: JetFile, descriptor: DeclarationDescriptor): Boolean
class object {
public val INSTANCE: ImportInsertHelper
@@ -41,6 +41,8 @@ public class JetCodeStyleSettings extends CustomCodeStyleSettings {
public boolean LBRACE_ON_NEXT_LINE = false;
public boolean PREFER_ALL_UNDER_IMPORTS = false;
public static JetCodeStyleSettings getInstance(Project project) {
return CodeStyleSettingsManager.getSettings(project).getCustomSettings(JetCodeStyleSettings.class);
}
@@ -20,11 +20,32 @@ import com.intellij.codeInsight.CodeInsightSettings
import com.intellij.codeInsight.actions.OptimizeImportsProcessor
import org.jetbrains.kotlin.idea.project.ProjectStructureUtil
import org.jetbrains.kotlin.js.analyze.TopDownAnalyzerFacadeForJS
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.*
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.ImportPath
import org.jetbrains.kotlin.resolve.jvm.TopDownAnalyzerFacadeForJVM
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor
import org.jetbrains.kotlin.descriptors.PackageViewDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.formatter.JetCodeStyleSettings
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered
import org.jetbrains.kotlin.descriptors.ClassifierDescriptor
import org.jetbrains.kotlin.idea.imports.importableFqName
import java.util.LinkedHashSet
import org.jetbrains.kotlin.idea.imports.importableFqNameSafe
import java.util.ArrayList
import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlin.resolve.scopes.JetScope
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
public class ImportInsertHelperImpl : ImportInsertHelper {
/**
@@ -53,28 +74,29 @@ public class ImportInsertHelperImpl : ImportInsertHelper {
}
}
override fun writeImportToFile(importPath: ImportPath, file: JetFile) {
override fun writeImportToFile(importPath: ImportPath, file: JetFile): JetImportDirective {
val psiFactory = JetPsiFactory(file.getProject())
if (file is JetCodeFragment) {
val newDirective = psiFactory.createImportDirective(importPath)
file.addImportsFromString(newDirective.getText())
return
return newDirective
}
val importList = file.getImportList()
if (importList != null) {
val newDirective = psiFactory.createImportDirective(importPath)
importList.add(psiFactory.createNewLine())
importList.add(newDirective)
return importList.add(newDirective) as JetImportDirective
}
else {
val newDirective = psiFactory.createImportDirectiveWithImportList(importPath)
val newImportList = psiFactory.createImportDirectiveWithImportList(importPath)
val packageDirective = file.getPackageDirective()
if (packageDirective == null) {
throw IllegalStateException("Scripts are not supported: " + file.getName())
}
packageDirective.getParent().addAfter(newDirective, packageDirective)
val addedImportList = packageDirective.getParent().addAfter(newImportList, packageDirective) as JetImportList
return addedImportList.getImports().single()
}
}
@@ -126,4 +148,220 @@ public class ImportInsertHelperImpl : ImportInsertHelper {
return true
}
override fun importDescriptor(file: JetFile, descriptor: DeclarationDescriptor): Boolean {
return Importer(file).importDescriptor(descriptor)
}
private inner class Importer(
private val file: JetFile
) {
private val resolutionFacade = file.getResolutionFacade()
private val preferAllUnderImports = JetCodeStyleSettings.getInstance(file.getProject()).PREFER_ALL_UNDER_IMPORTS
fun importDescriptor(descriptor: DeclarationDescriptor): Boolean {
val target = if (DescriptorUtils.isClassObject(descriptor)) // references to class object are treated as ones to its owner class
descriptor.getContainingDeclaration() as? ClassDescriptor ?: return false
else
descriptor
val name = target.getName()
val topLevelScope = resolutionFacade.getFileTopLevelScope(file)
// check if import is not needed
when (target) {
is ClassDescriptor -> { if (topLevelScope.getClassifier(name) == target) return true }
is PackageViewDescriptor -> { if (topLevelScope.getPackage(name) == target) return true }
is FunctionDescriptor -> { if (topLevelScope.getFunctions(name).contains(target)) return true }
is PropertyDescriptor -> { if (topLevelScope.getProperties(name).contains(target)) return true }
else -> return false
}
// do not insert imports for non-top level declarations
if (target !is PackageViewDescriptor && target.getContainingDeclaration() !is PackageFragmentDescriptor) return false
val imports = file.getImportDirectives()
//TODO: is that correct? What if function is imported and we need to import class?
if (imports.any { it.getImportedName() == name.asString() }) return false
val fqName = target.importableFqNameSafe
val packageFqName = fqName.parent()
val allUnderImportPath = ImportPath(packageFqName, true)
val tryAllUnderImport = preferAllUnderImports
&& !packageFqName.isRoot()
&& !imports.any { it.getImportPath() == allUnderImportPath }
&& when (target) {
is ClassDescriptor -> topLevelScope.getClassifier(name) == null // this check does not give a guarantee that import with * will import the class - for example, there can be classes with conflicting name in more than one import with *
is PackageViewDescriptor -> false
is FunctionDescriptor, is PropertyDescriptor -> true
else -> throw Exception()
}
if (tryAllUnderImport && addAllUnderImport(target)) {
return true
}
return addExplicitImport(target)
}
private fun addAllUnderImport(target: DeclarationDescriptor): Boolean {
val targetFqName = target.importableFqNameSafe
val parentFqName = targetFqName.parent()
val moduleDescriptor = resolutionFacade.findModuleDescriptor(file)
val imports = file.getImportDirectives()
val scopeToImport = getMemberScope(parentFqName, moduleDescriptor) ?: return false
val importedScopes = imports
.filter { it.isAllUnder () }
.map {
val importPath = it.getImportPath()
if (importPath != null) {
val fqName = importPath.fqnPart()
getMemberScope(fqName, moduleDescriptor)
}
else {
null
}
}
.filterNotNull()
val classNamesToImport = scopeToImport
.getDescriptorsFiltered(DescriptorKindFilter.CLASSIFIERS, { true })
.map { it.getName() }
val aliasNames = imports.map { it.getImportedName() }.filterNotNull().toSet()
//TODO: check for visibility
var conflictCandidates: List<ClassifierDescriptor> = classNamesToImport
.filter { it.asString() !in aliasNames }
.flatMap {
importedScopes.map { scope -> scope.getClassifier(it) }.filterNotNull()
}
val conflicts = detectNeededImports(conflictCandidates)
val addedImport = addImport(parentFqName, true)
if (target is ClassDescriptor) {
val newTopLevelScope = resolutionFacade.getFileTopLevelScope(file)
val resolvedTo = newTopLevelScope.getClassifier(target.getName())
if (resolvedTo?.importableFqNameSafe != targetFqName) {
addedImport.delete()
return false
}
}
for (conflict in conflicts) {
addImport(DescriptorUtils.getFqNameSafe(conflict), false)
}
dropRedundantExplicitImports(parentFqName)
return true
}
private fun getMemberScope(fqName: FqName, moduleDescriptor: ModuleDescriptor): JetScope? {
val packageView = moduleDescriptor.getPackage(fqName)
if (packageView != null) {
return packageView.getMemberScope()
}
val parentScope = getMemberScope(fqName.parent(), moduleDescriptor) ?: return null
val classDescriptor = parentScope.getClassifier(fqName.shortName()) as? ClassDescriptor ?: return null
return classDescriptor.getDefaultType().getMemberScope()
}
private fun addExplicitImport(target: DeclarationDescriptor): Boolean {
if (target is ClassDescriptor || target is PackageViewDescriptor) {
val topLevelScope = resolutionFacade.getFileTopLevelScope(file)
val name = target.getName()
val classifier = topLevelScope.getClassifier(name)
if (classifier != null && detectNeededImports(listOf(classifier)).isNotEmpty()) {
return false
}
}
addImport(target.importableFqNameSafe, false)
return true
}
private fun dropRedundantExplicitImports(packageFqName: FqName) {
val dropCandidates = file.getImportDirectives().filter {
!it.isAllUnder() && it.getAliasName() == null && it.getImportPath()?.fqnPart()?.parent() == packageFqName
}
val importsToCheck = ArrayList<FqName>()
for (import in dropCandidates) {
val importedReference = import.getImportedReference() ?: continue
val refExpr = (importedReference as JetDotQualifiedExpression).getSelectorExpression() as JetSimpleNameExpression
val targets = refExpr.resolveTargets()
if (targets.any { it is PackageViewDescriptor }) continue // do not drop import of package
val classDescriptor = targets.filterIsInstance<ClassDescriptor>().firstOrNull()
importsToCheck.addIfNotNull(classDescriptor?.importableFqNameSafe)
import.delete()
}
if (importsToCheck.isNotEmpty()) {
val topLevelScope = resolutionFacade.getFileTopLevelScope(file)
for (classFqName in importsToCheck) {
if (topLevelScope.getClassifier(classFqName.shortName())?.importableFqNameSafe != classFqName) {
addImport(classFqName, false) // restore explicit import
}
}
}
}
private fun detectNeededImports(importedDescriptors: Collection<DeclarationDescriptor>): Set<DeclarationDescriptor> {
if (importedDescriptors.isEmpty()) return setOf()
val descriptorsToCheck = importedDescriptors.map { it.getName() to it }.toMap().toLinkedMap()
val result = LinkedHashSet<DeclarationDescriptor>()
file.accept(object : JetVisitorVoid() {
override fun visitElement(element: PsiElement) {
if (descriptorsToCheck.isEmpty()) return
element.acceptChildren(this)
}
override fun visitImportList(importList: JetImportList) {
}
override fun visitPackageDirective(directive: JetPackageDirective) {
}
override fun visitSimpleNameExpression(expression: JetSimpleNameExpression) {
if (JetPsiUtil.isSelectorInQualified(expression)) return
val refName = expression.getReferencedNameAsName()
val descriptor = descriptorsToCheck[refName]
if (descriptor != null) {
val targetFqName = targetFqName(expression)
if (targetFqName != null && targetFqName == DescriptorUtils.getFqNameSafe(descriptor)) {
descriptorsToCheck.remove(refName)
result.add(descriptor)
}
}
}
})
return result
}
private fun JetImportDirective.getImportedName(): String? = JetPsiUtil.getAliasName(this)?.getIdentifier()
private fun targetFqName(ref: JetReferenceExpression): FqName? {
return ref.resolveTargets().map { it.importableFqName }.toSet().singleOrNull()
}
private fun JetReferenceExpression.resolveTargets(): Collection<DeclarationDescriptor> {
val bindingContext = resolutionFacade.analyze(this, BodyResolveMode.PARTIAL)
return bindingContext[BindingContext.REFERENCE_TARGET, this]?.let { listOf(it) }
?: bindingContext[BindingContext.AMBIGUOUS_REFERENCE_TARGET, this]
?: return listOf()
}
private fun addImport(fqName: FqName, allUnder: Boolean): JetImportDirective {
return writeImportToFile(ImportPath(fqName, allUnder), file)
}
}
}
@@ -0,0 +1,6 @@
// IMPORT: class: kotlin.String
package p
import java.lang.String
fun foo(): ArrayList<String> {}
@@ -0,0 +1 @@
Failed to add import
@@ -0,0 +1,4 @@
// IMPORT: java.sql.Date
import java.util.*
val d: Date = Date()
@@ -0,0 +1 @@
Failed to add import
@@ -0,0 +1,4 @@
// IMPORT: java.sql.Date
import java.util as Date
val d: Date.Date = Date.Date()
@@ -0,0 +1 @@
Failed to add import
@@ -0,0 +1,6 @@
// IMPORT: java.util.ArrayList
package p
import java.util.*
fun foo(): ArrayList<String> {}
@@ -0,0 +1,6 @@
// IMPORT: java.util.ArrayList
package p
import java.util.*
fun foo(): ArrayList<String> {}
@@ -0,0 +1,6 @@
// IMPORT: java.util.ArrayList
package p
import java.util.ArrayList
fun foo(): ArrayList<String> {}
@@ -0,0 +1,6 @@
// IMPORT: java.util.ArrayList
package p
import java.util.ArrayList
fun foo(): ArrayList<String> {}
@@ -0,0 +1,6 @@
// IMPORT: java.util.ArrayList
package p
import java.sql.*
val d = Date()
@@ -0,0 +1,8 @@
// IMPORT: java.util.ArrayList
package p
import java.sql.*
import java.util.*
import java.sql.Date
val d = Date()
@@ -0,0 +1,6 @@
// IMPORT: java.util.ArrayList
package p
import java.sql.*
val d: Date? = null
@@ -0,0 +1,8 @@
// IMPORT: java.util.ArrayList
package p
import java.sql.*
import java.util.*
import java.sql.Date
val d: Date? = null
@@ -0,0 +1,5 @@
package dependency
class Date
fun Date(s: String) = Date()
@@ -0,0 +1,6 @@
// IMPORT: java.util.ArrayList
package p
import dependency.*
val d = Date(1, 2)
@@ -0,0 +1,8 @@
// IMPORT: java.util.ArrayList
package p
import dependency.*
import java.util.*
import dependency.Date
val d = Date(1, 2)
@@ -0,0 +1,7 @@
package dependency
class Date {
class object {
val VALUE = 0
}
}
@@ -0,0 +1,6 @@
// IMPORT: java.util.ArrayList
package p
import dependency.*
val d = Date.VALUE
@@ -0,0 +1,8 @@
// IMPORT: java.util.ArrayList
package p
import dependency.*
import java.util.*
import dependency.Date
val d = Date.VALUE
@@ -0,0 +1,5 @@
package dependency
class AClass {
class Date
}
@@ -0,0 +1,6 @@
// IMPORT: java.util.ArrayList
package p
import dependency.AClass.*
val d = Date()
@@ -0,0 +1,8 @@
// IMPORT: java.util.ArrayList
package p
import dependency.AClass.*
import java.util.*
import dependency.AClass.Date
val d = Date()
@@ -0,0 +1,6 @@
// IMPORT: java.util.ArrayList
package p
import java.sql.*
val d: java.sql.Date? = null
@@ -0,0 +1,7 @@
// IMPORT: java.util.ArrayList
package p
import java.sql.*
import java.util.*
val d: java.sql.Date? = null
@@ -0,0 +1,8 @@
// IMPORT: java.util.ArrayList
package p
import java.sql.*
class Date
val d: Date? = null
@@ -0,0 +1,9 @@
// IMPORT: java.util.ArrayList
package p
import java.sql.*
import java.util.*
class Date
val d: Date? = null
@@ -0,0 +1,3 @@
package dependency
class Date
@@ -0,0 +1,7 @@
// IMPORT: java.util.ArrayList
package p
import dependency.*
import java.sql.*
val d = Date()
@@ -0,0 +1,8 @@
// IMPORT: java.util.ArrayList
package p
import dependency.*
import java.sql.*
import java.util.*
val d = Date()
@@ -0,0 +1,6 @@
// IMPORT: java.util.ArrayList
package p
import java.sql.*
val d: Driver? = null
@@ -0,0 +1,7 @@
// IMPORT: java.util.ArrayList
package p
import java.sql.*
import java.util.*
val d: Driver? = null
@@ -0,0 +1,7 @@
// IMPORT: java.util.ArrayList
package p
import java.sql.*
import java.sql.Date
val d: Date? = null
@@ -0,0 +1,8 @@
// IMPORT: java.util.ArrayList
package p
import java.sql.*
import java.sql.Date
import java.util.*
val d: Date? = null
@@ -0,0 +1,4 @@
// IMPORT: java.sql.Date
package p
import java.util.*
@@ -0,0 +1,5 @@
// IMPORT: java.sql.Date
package p
import java.util.*
import java.sql.Date
@@ -0,0 +1,5 @@
// IMPORT: java.sql.Date
package p
import java.util.*
import java.sql.*
@@ -0,0 +1,6 @@
// IMPORT: java.sql.Date
package p
import java.util.*
import java.sql.*
import java.sql.Date
@@ -0,0 +1,3 @@
package dependency
class Date
@@ -0,0 +1,5 @@
// IMPORT: dependency.Date
package p
import java.util.*
import java.sql.*
@@ -0,0 +1,6 @@
// IMPORT: dependency.Date
package p
import java.util.*
import java.sql.*
import dependency.Date
@@ -0,0 +1,14 @@
// IMPORT: java.util.HashMap
package p
import java.sql.*
import java.util.ArrayList // maybe dropped on adding import with *
import java.util.Date // should not be dropped because of conflicting java.sql.Date class
import java.util.HashSet as JavaHashSet // alias import should not be dropped
import java.util.concurrent // import of package should not be dropped because packages are not imported by *
fun foo() {
val v1 = JavaHashSet()
val v2 = Date()
val v3 = ArrayList()
}
@@ -0,0 +1,14 @@
// IMPORT: java.util.HashMap
package p
import java.sql.*
import java.util.HashSet as JavaHashSet // alias import should not be dropped
import java.util.concurrent // import of package should not be dropped because packages are not imported by *
import java.util.*
import java.util.Date
fun foo() {
val v1 = JavaHashSet()
val v2 = Date()
val v3 = ArrayList()
}
@@ -0,0 +1,6 @@
package dependency
fun function(){}
val property: Int = 0
class Class1
class Class2
@@ -0,0 +1,6 @@
// IMPORT: dependency.Class2
package p
import dependency.Class1
import dependency.function
import dependency.property
@@ -0,0 +1,4 @@
// IMPORT: dependency.Class2
package p
import dependency.*
@@ -0,0 +1,3 @@
package dependency
fun foo(){}
@@ -0,0 +1,8 @@
// IMPORT: dependency.foo
package p
import dependency.foo
fun f() {
foo()
}
@@ -0,0 +1,8 @@
// IMPORT: dependency.foo
package p
import dependency.foo
fun f() {
foo()
}
@@ -0,0 +1,3 @@
package dependency
fun foo(){}
@@ -0,0 +1,8 @@
// IMPORT: dependency.foo
package p
import dependency.*
fun f() {
foo()
}
@@ -0,0 +1,8 @@
// IMPORT: dependency.foo
package p
import dependency.*
fun f() {
foo()
}
@@ -0,0 +1,4 @@
// IMPORT: java.util.ArrayList
package p
fun foo() {}
@@ -0,0 +1,6 @@
// IMPORT: java.util.ArrayList
package p
import java.util.*
fun foo() {}
+4
View File
@@ -0,0 +1,4 @@
// IMPORT: java.util
package p
fun foo(): util.ArrayList<String> {}
@@ -0,0 +1,6 @@
// IMPORT: java.util
package p
import java.util
fun foo(): util.ArrayList<String> {}
@@ -0,0 +1,6 @@
// IMPORT: class: kotlin.String
package p
import java.util.*
fun foo(): ArrayList<String> {}
@@ -0,0 +1,6 @@
// IMPORT: class: kotlin.String
package p
import java.util.*
fun foo(): ArrayList<String> {}
@@ -0,0 +1,6 @@
// IMPORT: java.util
package p
import java.util
fun foo(): util.ArrayList<String> {}
@@ -0,0 +1,6 @@
// IMPORT: java.util
package p
import java.util
fun foo(): util.ArrayList<String> {}
@@ -0,0 +1,3 @@
package dependency
val foo: Int = 1
@@ -0,0 +1,6 @@
// IMPORT: dependency.foo
package p
import dependency.foo
fun f() = foo
@@ -0,0 +1,6 @@
// IMPORT: dependency.foo
package p
import dependency.foo
fun f() = foo
@@ -0,0 +1,3 @@
package dependency
val foo: Int = 1
@@ -0,0 +1,6 @@
// IMPORT: dependency.foo
package p
import dependency.*
fun f() = foo
@@ -0,0 +1,6 @@
// IMPORT: dependency.foo
package p
import dependency.*
fun f() = foo
@@ -1,4 +1,4 @@
// OPTIMIZE_IMPORTS
// OPTIMIZE_IMPORTS: true
import java.util.Date
val x = <selection>java.sql.Date(1)</selection>
@@ -1,4 +1,4 @@
// OPTIMIZE_IMPORTS
// OPTIMIZE_IMPORTS: true
import java.sql.Date
val x = Date(1)
@@ -1,4 +1,4 @@
// OPTIMIZE_IMPORTS
// OPTIMIZE_IMPORTS: true
import java.util.Date
class A : <selection>java.sql.Date</selection>
@@ -1,4 +1,4 @@
// OPTIMIZE_IMPORTS
// OPTIMIZE_IMPORTS: true
import java.sql.Date
class A : Date
@@ -1,4 +1,4 @@
// OPTIMIZE_IMPORTS
// OPTIMIZE_IMPORTS: true
import java.io.*
<selection>class A(val d: java.sql.Date, val rs: java.sql.ResultSet)</selection>
@@ -1,4 +1,4 @@
// OPTIMIZE_IMPORTS
// OPTIMIZE_IMPORTS: true
import java.sql.Date
import java.sql.ResultSet
@@ -0,0 +1,77 @@
/*
* Copyright 2010-2015 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
import org.jetbrains.kotlin.idea.JetLightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.test.JetTestUtils
import org.jetbrains.kotlin.idea.JetWithJdkAndRuntimeLightProjectDescriptor
import com.intellij.codeInsight.CodeInsightSettings
import org.jetbrains.kotlin.idea.formatter.JetCodeStyleSettings
import java.io.File
import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.application.ApplicationManager
import org.jetbrains.kotlin.idea.codeInsight.ShortenReferences
public abstract class AbstractImportsTest : JetLightCodeInsightFixtureTestCase() {
override fun getTestDataPath() = JetTestUtils.getHomeDirectory()
override fun getProjectDescriptor() = JetWithJdkAndRuntimeLightProjectDescriptor.INSTANCE
protected fun doTest(testPath: String) {
val codeInsightSettings = CodeInsightSettings.getInstance()
val codeStyleSettings = JetCodeStyleSettings.getInstance(getProject())
val optimizeImportsBefore = codeInsightSettings.OPTIMIZE_IMPORTS_ON_THE_FLY
val preferAllUnderBefore = codeStyleSettings.PREFER_ALL_UNDER_IMPORTS
try {
val fixture = myFixture
val dependencyPath = testPath.replace(".kt", ".dependency.kt")
if (File(dependencyPath).exists()) {
fixture.configureByFile(dependencyPath)
}
val javaDependencyPath = testPath.replace(".kt", ".dependency.java")
if (File(javaDependencyPath).exists()) {
fixture.configureByFile(javaDependencyPath)
}
fixture.configureByFile(testPath)
val file = fixture.getFile() as JetFile
codeInsightSettings.OPTIMIZE_IMPORTS_ON_THE_FLY = InTextDirectivesUtils.getPrefixedBoolean(file.getText(), "// OPTIMIZE_IMPORTS:") ?: false
codeStyleSettings.PREFER_ALL_UNDER_IMPORTS = InTextDirectivesUtils.getPrefixedBoolean(file.getText(), "// ALL_UNDER_IMPORTS:") ?: preferAllUnderImportsDefault
CommandProcessor.getInstance().executeCommand(getProject(), {
ApplicationManager.getApplication()!!.runWriteAction {
doTest(file)
}
}, null, null)
fixture.checkResultByFile(testPath + ".after")
}
finally {
codeInsightSettings.OPTIMIZE_IMPORTS_ON_THE_FLY = optimizeImportsBefore
codeStyleSettings.PREFER_ALL_UNDER_IMPORTS = preferAllUnderBefore
}
}
protected abstract fun doTest(file: JetFile)
protected open val preferAllUnderImportsDefault: Boolean
get() = true
}
@@ -0,0 +1,69 @@
/*
* Copyright 2010-2015 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.addImport
import org.jetbrains.kotlin.AbstractImportsTest
import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.quickfix.ImportInsertHelper
import org.jetbrains.kotlin.psi.JetPsiFactory
import org.jetbrains.kotlin.resolve.QualifiedExpressionResolver
import org.jetbrains.kotlin.resolve.QualifiedExpressionResolver.LookupMode
import org.jetbrains.kotlin.resolve.JetModuleUtil
import org.jetbrains.kotlin.resolve.BindingTraceContext
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import com.intellij.psi.PsiDocumentManager
public abstract class AbstractAddImportTest : AbstractImportsTest() {
override fun doTest(file: JetFile) {
var descriptorName = InTextDirectivesUtils.findStringWithPrefixes(file.getText(), "// IMPORT:")
?: error("No IMPORT directive defined")
var filter: (DeclarationDescriptor) -> Boolean = { true }
if (descriptorName.startsWith("class:")) {
filter = { it is ClassDescriptor }
descriptorName = descriptorName.substring("class:".length()).trim()
}
val importDirective = JetPsiFactory(getProject()).createImportDirective(descriptorName)
val moduleDescriptor = file.getResolutionFacade().findModuleDescriptor(file)
val scope = JetModuleUtil.getSubpackagesOfRootScope(moduleDescriptor)
val descriptors = QualifiedExpressionResolver()
.processImportReference(importDirective, scope, scope, null, BindingTraceContext(), LookupMode.EVERYTHING)
.filter(filter)
when {
descriptors.isEmpty() ->
error("No descriptor $descriptorName found")
descriptors.size() > 1 ->
error("Multiple descriptors found:\n " + descriptors.map { DescriptorRenderer.FQ_NAMES_IN_TYPES.render(it) }.joinToString("\n "))
else -> {
val success = ImportInsertHelper.INSTANCE.importDescriptor(file, descriptors.single())
if (!success) {
val document = PsiDocumentManager.getInstance(getProject()).getDocument(file)
document.replaceString(0, document.getTextLength(), "Failed to add import")
PsiDocumentManager.getInstance(getProject()).commitAllDocuments()
}
}
}
}
}
@@ -0,0 +1,206 @@
/*
* Copyright 2010-2015 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.addImport;
import com.intellij.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.InnerTestClasses;
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
import org.jetbrains.kotlin.test.JetTestUtils;
import org.jetbrains.kotlin.test.TestMetadata;
import org.junit.runner.RunWith;
import java.io.File;
import java.util.regex.Pattern;
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("idea/testData/addImport")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public class AddImportTestGenerated extends AbstractAddImportTest {
public void testAllFilesPresentInAddImport() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/addImport"), Pattern.compile("^([^\\.]+)\\.kt$"), true);
}
@TestMetadata("CannotImportClass1.kt")
public void testCannotImportClass1() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/addImport/CannotImportClass1.kt");
doTest(fileName);
}
@TestMetadata("CannotImportClass2.kt")
public void testCannotImportClass2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/addImport/CannotImportClass2.kt");
doTest(fileName);
}
@TestMetadata("CannotImportClass3.kt")
public void testCannotImportClass3() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/addImport/CannotImportClass3.kt");
doTest(fileName);
}
@TestMetadata("ClassAlreadyImported1.kt")
public void testClassAlreadyImported1() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/addImport/ClassAlreadyImported1.kt");
doTest(fileName);
}
@TestMetadata("ClassAlreadyImported2.kt")
public void testClassAlreadyImported2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/addImport/ClassAlreadyImported2.kt");
doTest(fileName);
}
@TestMetadata("ConflictingNameAppearsAndHasUsage1.kt")
public void testConflictingNameAppearsAndHasUsage1() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/addImport/ConflictingNameAppearsAndHasUsage1.kt");
doTest(fileName);
}
@TestMetadata("ConflictingNameAppearsAndHasUsage2.kt")
public void testConflictingNameAppearsAndHasUsage2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/addImport/ConflictingNameAppearsAndHasUsage2.kt");
doTest(fileName);
}
@TestMetadata("ConflictingNameAppearsAndHasUsage3.kt")
public void testConflictingNameAppearsAndHasUsage3() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/addImport/ConflictingNameAppearsAndHasUsage3.kt");
doTest(fileName);
}
@TestMetadata("ConflictingNameAppearsAndHasUsage4.kt")
public void testConflictingNameAppearsAndHasUsage4() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/addImport/ConflictingNameAppearsAndHasUsage4.kt");
doTest(fileName);
}
@TestMetadata("ConflictingNameAppearsAndHasUsage5.kt")
public void testConflictingNameAppearsAndHasUsage5() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/addImport/ConflictingNameAppearsAndHasUsage5.kt");
doTest(fileName);
}
@TestMetadata("ConflictingNameAppearsButUsageIsQualified.kt")
public void testConflictingNameAppearsButUsageIsQualified() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/addImport/ConflictingNameAppearsButUsageIsQualified.kt");
doTest(fileName);
}
@TestMetadata("ConflictingNameAppearsFalseUsage.kt")
public void testConflictingNameAppearsFalseUsage() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/addImport/ConflictingNameAppearsFalseUsage.kt");
doTest(fileName);
}
@TestMetadata("ConflictingNameAppearsFalseUsage2.kt")
public void testConflictingNameAppearsFalseUsage2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/addImport/ConflictingNameAppearsFalseUsage2.kt");
doTest(fileName);
}
@TestMetadata("ConflictingNameAppearsNoUsage.kt")
public void testConflictingNameAppearsNoUsage() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/addImport/ConflictingNameAppearsNoUsage.kt");
doTest(fileName);
}
@TestMetadata("ConflictingNameHasExplicitImportAlready.kt")
public void testConflictingNameHasExplicitImportAlready() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/addImport/ConflictingNameHasExplicitImportAlready.kt");
doTest(fileName);
}
@TestMetadata("ConflictingNameNoAllUnderImport.kt")
public void testConflictingNameNoAllUnderImport() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/addImport/ConflictingNameNoAllUnderImport.kt");
doTest(fileName);
}
@TestMetadata("ConflictingNameNoAllUnderImport2.kt")
public void testConflictingNameNoAllUnderImport2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/addImport/ConflictingNameNoAllUnderImport2.kt");
doTest(fileName);
}
@TestMetadata("ConflictingNameNoAllUnderImport3.kt")
public void testConflictingNameNoAllUnderImport3() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/addImport/ConflictingNameNoAllUnderImport3.kt");
doTest(fileName);
}
@TestMetadata("DropExplicitImports.kt")
public void testDropExplicitImports() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/addImport/DropExplicitImports.kt");
doTest(fileName);
}
@TestMetadata("DropExplicitImports2.kt")
public void testDropExplicitImports2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/addImport/DropExplicitImports2.kt");
doTest(fileName);
}
@TestMetadata("FunctionAlreadyImported1.kt")
public void testFunctionAlreadyImported1() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/addImport/FunctionAlreadyImported1.kt");
doTest(fileName);
}
@TestMetadata("FunctionAlreadyImported2.kt")
public void testFunctionAlreadyImported2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/addImport/FunctionAlreadyImported2.kt");
doTest(fileName);
}
@TestMetadata("ImportClassSimple.kt")
public void testImportClassSimple() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/addImport/ImportClassSimple.kt");
doTest(fileName);
}
@TestMetadata("ImportPackage.kt")
public void testImportPackage() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/addImport/ImportPackage.kt");
doTest(fileName);
}
@TestMetadata("NoNeedToImportStandardClass.kt")
public void testNoNeedToImportStandardClass() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/addImport/NoNeedToImportStandardClass.kt");
doTest(fileName);
}
@TestMetadata("PackageAlreadyImported.kt")
public void testPackageAlreadyImported() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/addImport/PackageAlreadyImported.kt");
doTest(fileName);
}
@TestMetadata("PropertyAlreadyImported1.kt")
public void testPropertyAlreadyImported1() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/addImport/PropertyAlreadyImported1.kt");
doTest(fileName);
}
@TestMetadata("PropertyAlreadyImported2.kt")
public void testPropertyAlreadyImported2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/addImport/PropertyAlreadyImported2.kt");
doTest(fileName);
}
}
@@ -17,55 +17,17 @@
package org.jetbrains.kotlin.shortenRefs
import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.idea.JetWithJdkAndRuntimeLightProjectDescriptor
import com.intellij.openapi.application.ApplicationManager
import java.io.File
import com.intellij.openapi.command.CommandProcessor
import org.jetbrains.kotlin.idea.codeInsight.ShortenReferences
import org.jetbrains.kotlin.idea.JetLightCodeInsightFixtureTestCase
import com.intellij.codeInsight.CodeInsightSettings
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.jetbrains.kotlin.test.JetTestUtils
import org.jetbrains.kotlin.AbstractImportsTest
public abstract class AbstractShortenRefsTest : JetLightCodeInsightFixtureTestCase() {
override fun getTestDataPath() = JetTestUtils.getHomeDirectory()
override fun getProjectDescriptor() = JetWithJdkAndRuntimeLightProjectDescriptor.INSTANCE
protected fun doTest(testPath: String) {
val codeInsightSettings = CodeInsightSettings.getInstance()
val optimizeImportsBefore = codeInsightSettings.OPTIMIZE_IMPORTS_ON_THE_FLY
try {
val fixture = myFixture
val dependencyPath = testPath.replace(".kt", ".dependency.kt")
if (File(dependencyPath).exists()) {
fixture.configureByFile(dependencyPath)
}
val javaDependencyPath = testPath.replace(".kt", ".dependency.java")
if (File(javaDependencyPath).exists()) {
fixture.configureByFile(javaDependencyPath)
}
fixture.configureByFile(testPath)
val file = fixture.getFile() as JetFile
val selectionModel = fixture.getEditor().getSelectionModel()
if (!selectionModel.hasSelection()) error("No selection in input file")
codeInsightSettings.OPTIMIZE_IMPORTS_ON_THE_FLY =
InTextDirectivesUtils.isDirectiveDefined(file.getText(), "// OPTIMIZE_IMPORTS")
CommandProcessor.getInstance().executeCommand(getProject(), {
ApplicationManager.getApplication()!!.runWriteAction {
ShortenReferences.process(file, selectionModel.getSelectionStart(), selectionModel.getSelectionEnd())
}
}, null, null)
selectionModel.removeSelection()
fixture.checkResultByFile(testPath + ".after")
}
finally {
codeInsightSettings.OPTIMIZE_IMPORTS_ON_THE_FLY = optimizeImportsBefore
}
public abstract class AbstractShortenRefsTest : AbstractImportsTest() {
override fun doTest(file: JetFile) {
val selectionModel = myFixture.getEditor().getSelectionModel()
if (!selectionModel.hasSelection()) error("No selection in input file")
ShortenReferences.process(file, selectionModel.getSelectionStart(), selectionModel.getSelectionEnd())
selectionModel.removeSelection()
}
override val preferAllUnderImportsDefault: Boolean
get() = false
}