More sophisticated import setting supported: *-import when more than N names used (default set to 5)
This commit is contained in:
@@ -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
-1
@@ -1,4 +1,4 @@
|
||||
// ALL_UNDER_IMPORTS: true
|
||||
// NAME_COUNT_TO_USE_STAR_IMPORT: 1
|
||||
package ppp
|
||||
|
||||
import java.util.ArrayList
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// ALL_UNDER_IMPORTS: true
|
||||
// NAME_COUNT_TO_USE_STAR_IMPORT: 1
|
||||
package ppp
|
||||
|
||||
import java.util.*
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// ALL_UNDER_IMPORTS: true
|
||||
// NAME_COUNT_TO_USE_STAR_IMPORT: 1
|
||||
package ppp
|
||||
|
||||
import java.util.HashSet
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// ALL_UNDER_IMPORTS: true
|
||||
// NAME_COUNT_TO_USE_STAR_IMPORT: 1
|
||||
package ppp
|
||||
|
||||
import java.util.*
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// ALL_UNDER_IMPORTS: true
|
||||
// NAME_COUNT_TO_USE_STAR_IMPORT: 1
|
||||
package ppp
|
||||
|
||||
import java.sql.Array
|
||||
|
||||
+1
-1
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user