More sophisticated import setting supported: *-import when more than N names used (default set to 5)

This commit is contained in:
Valentin Kipyatkov
2015-02-18 19:43:09 +03:00
parent c58536f1d9
commit a137503ee9
29 changed files with 157 additions and 92 deletions
@@ -42,7 +42,7 @@ public class JetCodeStyleSettings extends CustomCodeStyleSettings {
public boolean LBRACE_ON_NEXT_LINE = false;
public boolean PREFER_ALL_UNDER_IMPORTS = !ApplicationManager.getApplication().isUnitTestMode();
public int NAME_COUNT_TO_USE_STAR_IMPORT = ApplicationManager.getApplication().isUnitTestMode() ? Integer.MAX_VALUE : 5;
public boolean IMPORT_PACKAGES = true;
public static JetCodeStyleSettings getInstance(Project project) {
@@ -68,76 +68,66 @@ public class KotlinImportOptimizer() : ImportOptimizer {
val descriptorsToImport = detectDescriptorsToImport()
val importsToGenerate = HashSet<ImportPath>()
if (codeStyleSettings.PREFER_ALL_UNDER_IMPORTS) {
val packageNames = HashMap<FqName, Int>() // package name to symbols to import count
fun incUseCount(packageName: FqName): Int {
val count = packageNames.getOrPut(packageName, { 0 }) + 1
packageNames.put(packageName, count)
return count
val descriptorsByPackages = HashMap<FqName, MutableCollection<DeclarationDescriptor>>()
for (descriptor in descriptorsToImport) {
val fqName = descriptor.importableFqNameSafe
val parentFqName = fqName.parent()
if (descriptor is PackageViewDescriptor || parentFqName.isRoot()) {
importsToGenerate.add(ImportPath(fqName, false))
}
fun decUseCount(packageName: FqName): Int {
val count = packageNames[packageName]!! - 1
if (count > 0) {
packageNames[packageName] = count
}
else {
packageNames.remove(packageName)
}
return count
}
val classNames = HashSet<FqName>()
val builder = StringBuilder()
builder.append("package ").append(IdeDescriptorRenderers.SOURCE_CODE.renderFqName(currentPackageName)).append("\n")
for (descriptor in descriptorsToImport) {
val fqName = descriptor.importableFqNameSafe
val parentFqName = fqName.parent()
if (descriptor is PackageViewDescriptor || parentFqName.isRoot()) {
importsToGenerate.add(ImportPath(fqName, false))
}
else {
val useCount = incUseCount(parentFqName)
if (descriptor is ClassDescriptor) {
classNames.add(fqName)
}
if (parentFqName == currentPackageName) continue // in current package
if (importInsertHelper.isImportedWithDefault(ImportPath(fqName, false), file)) continue
if (useCount == 1) {
builder.append("import ").append(IdeDescriptorRenderers.SOURCE_CODE.renderFqName(parentFqName)).append(".*\n")
importsToGenerate.add(ImportPath(parentFqName, true))
}
}
}
// now check that all classes are really imported
val fileWithImports = JetPsiFactory(file).createAnalyzableFile("Dummy.kt", builder.toString(), file)
val scope = fileWithImports.getResolutionFacade().getFileTopLevelScope(fileWithImports)
for (fqName in classNames) {
if (scope.getClassifier(fqName.shortName())?.importableFqNameSafe != fqName) {
// add explicit import if failed to import with * (or from current package)
importsToGenerate.add(ImportPath(fqName, false))
val packageName = fqName.parent()
if (decUseCount(packageName) == 0) { // all under import is not really needed
importsToGenerate.remove(ImportPath(packageName, true))
}
}
else {
descriptorsByPackages.getOrPut(parentFqName, { ArrayList() }).add(descriptor)
}
}
else {
for (descriptor in descriptorsToImport) {
val fqName = descriptor.importableFqNameSafe
if (descriptor !is PackageViewDescriptor && fqName.parent() == currentPackageName) continue // in current package
val importPath = ImportPath(fqName, false)
if (importInsertHelper.isImportedWithDefault(importPath, file)) continue
importsToGenerate.add(importPath)
val builder = StringBuilder()
builder.append("package ").append(IdeDescriptorRenderers.SOURCE_CODE.renderFqName(currentPackageName)).append("\n")
val classNamesToCheck = HashSet<FqName>()
for ((packageName, descriptors) in descriptorsByPackages) {
val fqNames = descriptors.map { it.importableFqNameSafe }.toSet()
val explicitImports = packageName != currentPackageName && fqNames.size() < codeStyleSettings.NAME_COUNT_TO_USE_STAR_IMPORT
if (explicitImports) {
for (fqName in fqNames) {
if (!importInsertHelper.isImportedWithDefault(ImportPath(fqName, false), file)) {
importsToGenerate.add(ImportPath(fqName, false))
}
}
}
else {
for (descriptor in descriptors) {
if (descriptor is ClassDescriptor) {
classNamesToCheck.add(descriptor.importableFqNameSafe)
}
}
if (packageName == currentPackageName) continue
if (fqNames.all { fqName -> importInsertHelper.isImportedWithDefault(ImportPath(fqName, false), file) }) continue
builder.append("import ").append(IdeDescriptorRenderers.SOURCE_CODE.renderFqName(packageName)).append(".*\n")
importsToGenerate.add(ImportPath(packageName, true))
}
}
// now check that all classes are really imported
val fileWithImports = JetPsiFactory(file).createAnalyzableFile("Dummy.kt", builder.toString(), file)
val scope = fileWithImports.getResolutionFacade().getFileTopLevelScope(fileWithImports)
for (fqName in classNamesToCheck) {
if (scope.getClassifier(fqName.shortName())?.importableFqNameSafe != fqName) {
// add explicit import if failed to import with * (or from current package)
importsToGenerate.add(ImportPath(fqName, false))
val packageName = fqName.parent()
val descriptors = descriptorsByPackages[packageName]
descriptors.removeAll(descriptors.filter { it.importableFqNameSafe == fqName })
descriptorsByPackages[packageName] = descriptors
if (descriptors.isEmpty()) { // star import is not really needed
importsToGenerate.remove(ImportPath(packageName, true))
}
}
}
@@ -152,7 +152,7 @@ public class ImportInsertHelperImpl(private val project: Project) : ImportInsert
private val file: JetFile
) {
private val resolutionFacade = file.getResolutionFacade()
private val preferAllUnderImports = JetCodeStyleSettings.getInstance(project).PREFER_ALL_UNDER_IMPORTS
private val nameCountToUseStarImport = JetCodeStyleSettings.getInstance(project).NAME_COUNT_TO_USE_STAR_IMPORT
fun importDescriptor(descriptor: DeclarationDescriptor): ImportDescriptorResult {
val target = descriptor.getImportableDescriptor()
@@ -188,8 +188,13 @@ public class ImportInsertHelperImpl(private val project: Project) : ImportInsert
val fqName = target.importableFqNameSafe
val packageFqName = fqName.parent()
val importsFromPackage = imports.count {
val path = it.getImportPath()
path != null && !path.isAllUnder() && !path.hasAlias() && path.fqnPart().parent() == packageFqName
}
val allUnderImportPath = ImportPath(packageFqName, true)
val tryAllUnderImport = preferAllUnderImports
val tryAllUnderImport = importsFromPackage + 1 >= nameCountToUseStarImport
&& !packageFqName.isRoot()
&& !imports.any { it.getImportPath() == allUnderImportPath }
&& when (target) {
@@ -0,0 +1,5 @@
// IMPORT: java.util.Date
// NAME_COUNT_TO_USE_STAR_IMPORT: 3
package p
import java.util.HashMap
@@ -0,0 +1,6 @@
// IMPORT: java.util.Date
// NAME_COUNT_TO_USE_STAR_IMPORT: 3
package p
import java.util.Date
import java.util.HashMap
@@ -0,0 +1,6 @@
// IMPORT: java.util.Date
// NAME_COUNT_TO_USE_STAR_IMPORT: 3
package p
import java.util.HashMap
import java.util.ArrayList
@@ -0,0 +1,5 @@
// IMPORT: java.util.Date
// NAME_COUNT_TO_USE_STAR_IMPORT: 3
package p
import java.util.*
@@ -1,4 +1,4 @@
// ALL_UNDER_IMPORTS: true
// NAME_COUNT_TO_USE_STAR_IMPORT: 1
package ppp
import java.util.HashMap
@@ -1,4 +1,4 @@
// ALL_UNDER_IMPORTS: true
// NAME_COUNT_TO_USE_STAR_IMPORT: 1
package ppp
import java.sql.Date
@@ -1,4 +1,4 @@
// ALL_UNDER_IMPORTS: true
// NAME_COUNT_TO_USE_STAR_IMPORT: 1
package ppp
import java.util.HashMap
@@ -1,4 +1,4 @@
// ALL_UNDER_IMPORTS: true
// NAME_COUNT_TO_USE_STAR_IMPORT: 1
package ppp
import java.sql.*
@@ -1,4 +1,4 @@
// ALL_UNDER_IMPORTS: true
// NAME_COUNT_TO_USE_STAR_IMPORT: 1
package ppp
import java.util.ArrayList
@@ -1,4 +1,4 @@
// ALL_UNDER_IMPORTS: true
// NAME_COUNT_TO_USE_STAR_IMPORT: 1
package ppp
import java.util.*
@@ -1,4 +1,4 @@
// ALL_UNDER_IMPORTS: true
// NAME_COUNT_TO_USE_STAR_IMPORT: 1
package ppp
import java.util.HashSet
@@ -1,4 +1,4 @@
// ALL_UNDER_IMPORTS: true
// NAME_COUNT_TO_USE_STAR_IMPORT: 1
package ppp
import java.util.*
@@ -1,4 +1,4 @@
// ALL_UNDER_IMPORTS: true
// NAME_COUNT_TO_USE_STAR_IMPORT: 1
package ppp
import java.sql.Array
@@ -1,4 +1,4 @@
// ALL_UNDER_IMPORTS: true
// NAME_COUNT_TO_USE_STAR_IMPORT: 1
package ppp
import java.sql.*
@@ -0,0 +1,4 @@
package dependency
fun String.extFun() {}
fun String.extFun(p: Int) {}
@@ -0,0 +1,14 @@
// NAME_COUNT_TO_USE_STAR_IMPORT: 2
package ppp
import java.util.HashMap
import java.util.ArrayList
import java.io.File
import java.io.PrintStream
import dependency.*
fun foo(c: C) {
val v = HashMap<File, ArrayList<Int>>()
"".extFun()
"".extFun(1)
}
@@ -0,0 +1,12 @@
// NAME_COUNT_TO_USE_STAR_IMPORT: 2
package ppp
import dependency.extFun
import java.io.File
import java.util.*
fun foo(c: C) {
val v = HashMap<File, ArrayList<Int>>()
"".extFun()
"".extFun(1)
}
@@ -1,4 +1,4 @@
// ALL_UNDER_IMPORTS: true
// NAME_COUNT_TO_USE_STAR_IMPORT: 1
package ppp
import java.util.HashMap
@@ -1,4 +1,4 @@
// ALL_UNDER_IMPORTS: true
// NAME_COUNT_TO_USE_STAR_IMPORT: 1
package ppp
import java.io.*
@@ -1,4 +1,4 @@
// ALL_UNDER_IMPORTS: true
// NAME_COUNT_TO_USE_STAR_IMPORT: 1
package foo
<selection>fun foo(p: bar.Class1): bar.Class2 { }</selection>
@@ -1,4 +1,4 @@
// ALL_UNDER_IMPORTS: true
// NAME_COUNT_TO_USE_STAR_IMPORT: 1
package foo
import bar.*
@@ -36,7 +36,7 @@ public abstract class AbstractImportsTest : JetLightCodeInsightFixtureTestCase()
val codeInsightSettings = CodeInsightSettings.getInstance()
val codeStyleSettings = JetCodeStyleSettings.getInstance(getProject())
val optimizeImportsBefore = codeInsightSettings.OPTIMIZE_IMPORTS_ON_THE_FLY
val preferAllUnderBefore = codeStyleSettings.PREFER_ALL_UNDER_IMPORTS
val nameCountToUseStarBefore = codeStyleSettings.NAME_COUNT_TO_USE_STAR_IMPORT
try {
val fixture = myFixture
@@ -54,7 +54,7 @@ public abstract class AbstractImportsTest : JetLightCodeInsightFixtureTestCase()
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
codeStyleSettings.NAME_COUNT_TO_USE_STAR_IMPORT = InTextDirectivesUtils.getPrefixedInt(file.getText(), "// NAME_COUNT_TO_USE_STAR_IMPORT:") ?: nameCountToUseStarImportDefault
CommandProcessor.getInstance().executeCommand(getProject(), {
ApplicationManager.getApplication()!!.runWriteAction {
@@ -66,12 +66,12 @@ public abstract class AbstractImportsTest : JetLightCodeInsightFixtureTestCase()
}
finally {
codeInsightSettings.OPTIMIZE_IMPORTS_ON_THE_FLY = optimizeImportsBefore
codeStyleSettings.PREFER_ALL_UNDER_IMPORTS = preferAllUnderBefore
codeStyleSettings.NAME_COUNT_TO_USE_STAR_IMPORT = nameCountToUseStarBefore
}
}
protected abstract fun doTest(file: JetFile)
protected open val preferAllUnderImportsDefault: Boolean
get() = true
protected open val nameCountToUseStarImportDefault: Int
get() = 1
}
@@ -186,6 +186,18 @@ public class AddImportTestGenerated extends AbstractAddImportTest {
doTest(fileName);
}
@TestMetadata("NameCountForStarNotReached.kt")
public void testNameCountForStarNotReached() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/addImport/NameCountForStarNotReached.kt");
doTest(fileName);
}
@TestMetadata("NameCountForStarReached.kt")
public void testNameCountForStarReached() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/addImport/NameCountForStarReached.kt");
doTest(fileName);
}
@TestMetadata("NoConflictingNameForInaccessibleClass1.kt")
public void testNoConflictingNameForInaccessibleClass1() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/addImport/NoConflictingNameForInaccessibleClass1.kt");
@@ -29,6 +29,6 @@ public abstract class AbstractOptimizeImportsTest() : AbstractImportsTest() {
KotlinImportOptimizer().processFile(file).run()
}
override val preferAllUnderImportsDefault: Boolean
get() = false
override val nameCountToUseStarImportDefault: Int
get() = Integer.MAX_VALUE
}
@@ -219,6 +219,12 @@ public class OptimizeImportsTestGenerated extends AbstractOptimizeImportsTest {
doTest(fileName);
}
@TestMetadata("NameCountSetting.kt")
public void testNameCountSetting() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/editor/optimizeImports/allUnderImports/NameCountSetting.kt");
doTest(fileName);
}
@TestMetadata("Simple.kt")
public void testSimple() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/editor/optimizeImports/allUnderImports/Simple.kt");
@@ -29,6 +29,6 @@ public abstract class AbstractShortenRefsTest : AbstractImportsTest() {
selectionModel.removeSelection()
}
override val preferAllUnderImportsDefault: Boolean
get() = false
override val nameCountToUseStarImportDefault: Int
get() = Integer.MAX_VALUE
}