Code style setting for importing Java statics and enums with '*'

#KT-9592 Fixed
This commit is contained in:
Valentin Kipyatkov
2015-10-19 13:21:21 +03:00
parent 27228c2fcc
commit 115d7a2eae
19 changed files with 206 additions and 76 deletions
@@ -43,6 +43,7 @@ public class JetCodeStyleSettings extends CustomCodeStyleSettings {
public boolean LBRACE_ON_NEXT_LINE = false; public boolean LBRACE_ON_NEXT_LINE = false;
public int NAME_COUNT_TO_USE_STAR_IMPORT = ApplicationManager.getApplication().isUnitTestMode() ? Integer.MAX_VALUE : 5; public int NAME_COUNT_TO_USE_STAR_IMPORT = ApplicationManager.getApplication().isUnitTestMode() ? Integer.MAX_VALUE : 5;
public int NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS = ApplicationManager.getApplication().isUnitTestMode() ? Integer.MAX_VALUE : 3;
public boolean IMPORT_NESTED_CLASSES = false; public boolean IMPORT_NESTED_CLASSES = false;
public final PackageEntryTable PACKAGES_TO_USE_STAR_IMPORTS = new PackageEntryTable(); public final PackageEntryTable PACKAGES_TO_USE_STAR_IMPORTS = new PackageEntryTable();
@@ -37,7 +37,7 @@ import javax.swing.table.AbstractTableModel
class ImportSettingsPanelWrapper(settings: CodeStyleSettings) : CodeStyleAbstractPanel(settings) { class ImportSettingsPanelWrapper(settings: CodeStyleSettings) : CodeStyleAbstractPanel(settings) {
private val importsPanel = ImportSettingsPanel(settings) private val importsPanel = ImportSettingsPanel(settings)
private fun CodeStyleSettings.kotlinSettings() = getCustomSettings(javaClass<JetCodeStyleSettings>()) private fun CodeStyleSettings.kotlinSettings() = getCustomSettings(JetCodeStyleSettings::class.java)
override fun getRightMargin() = throw UnsupportedOperationException() override fun getRightMargin() = throw UnsupportedOperationException()
@@ -61,11 +61,6 @@ class ImportSettingsPanelWrapper(settings: CodeStyleSettings) : CodeStyleAbstrac
} }
class ImportSettingsPanel(private val commonSettings: CodeStyleSettings) : JPanel() { class ImportSettingsPanel(private val commonSettings: CodeStyleSettings) : JPanel() {
private val rbUseSingleImports = JRadioButton("With single name")
private val rbUseStarImports = JRadioButton("With '*'")
private val rbUseStarImportsIfMore = JRadioButton("With '*' if more than ")
private val starImportLimitModel = SpinnerNumberModel(4, 1, 100, 1)
private val starImportLimitField = JSpinner(starImportLimitModel)
private val cbImportNestedClasses = JCheckBox("Insert imports for nested classes") private val cbImportNestedClasses = JCheckBox("Insert imports for nested classes")
private val starImportPackageEntryTable = PackageEntryTable() private val starImportPackageEntryTable = PackageEntryTable()
@@ -75,18 +70,21 @@ class ImportSettingsPanel(private val commonSettings: CodeStyleSettings) : JPane
} }
private val starImportPackageTable = ImportLayoutPanel.createTableForPackageEntries(starImportPackageEntryTable, dummyImportLayoutPanel) private val starImportPackageTable = ImportLayoutPanel.createTableForPackageEntries(starImportPackageEntryTable, dummyImportLayoutPanel)
private val nameCountToUseStarImportSelector = NameCountToUseStarImportSelector("Use imports:")
private val nameCountToUseStarImportForMembersSelector = NameCountToUseStarImportSelector("Use imports for Java statics and enums:")
init { init {
setLayout(BorderLayout()) setLayout(BorderLayout())
add(JBScrollPane(JPanel(GridBagLayout()).init { add(JBScrollPane(JPanel(GridBagLayout()).apply {
val constraints = GridBagConstraints().init { val constraints = GridBagConstraints().apply {
weightx = 1.0 weightx = 1.0
insets = Insets(0, 10, 10, 10) insets = Insets(0, 10, 10, 10)
} }
add(createGeneralOptionsPanel(), constraints.init { add(createGeneralOptionsPanel(), constraints.apply {
fill = GridBagConstraints.HORIZONTAL fill = GridBagConstraints.HORIZONTAL
gridy = 0 gridy = 0
}) })
add(PackagePanel.createPackagesPanel(starImportPackageTable, starImportPackageEntryTable), constraints.init { add(PackagePanel.createPackagesPanel(starImportPackageTable, starImportPackageEntryTable), constraints.apply {
gridy = 1 gridy = 1
fill = GridBagConstraints.BOTH fill = GridBagConstraints.BOTH
weighty = 1.0 weighty = 1.0
@@ -95,44 +93,16 @@ class ImportSettingsPanel(private val commonSettings: CodeStyleSettings) : JPane
} }
private fun createGeneralOptionsPanel(): JPanel { private fun createGeneralOptionsPanel(): JPanel {
ButtonGroup().init { return OptionGroup(ApplicationBundle.message("title.general")).apply {
add(rbUseSingleImports) add(nameCountToUseStarImportSelector.createPanel(), nameCountToUseStarImportForMembersSelector.createPanel())
add(rbUseStarImports)
add(rbUseStarImportsIfMore)
}
fun updateEnabled() {
starImportLimitField.setEnabled(rbUseStarImportsIfMore.isSelected())
}
rbUseStarImportsIfMore.addChangeListener { updateEnabled() }
updateEnabled()
return OptionGroup(ApplicationBundle.message("title.general")).init {
add(JLabel("Use imports:"))
add(rbUseSingleImports, true)
add(rbUseStarImports, true)
add(JPanel(GridBagLayout()).init {
val constraints = GridBagConstraints().init { gridx = GridBagConstraints.RELATIVE }
add(rbUseStarImportsIfMore, constraints)
add(starImportLimitField, constraints)
add(JLabel(" names used"), constraints.init { fill = GridBagConstraints.HORIZONTAL; weightx = 1.0 })
}, true)
add(cbImportNestedClasses) add(cbImportNestedClasses)
}.createPanel() }.createPanel()
} }
fun reset(settings: JetCodeStyleSettings) { fun reset(settings: JetCodeStyleSettings) {
when (settings.NAME_COUNT_TO_USE_STAR_IMPORT) { nameCountToUseStarImportSelector.value = settings.NAME_COUNT_TO_USE_STAR_IMPORT
Int.MAX_VALUE -> rbUseSingleImports.setSelected(true) nameCountToUseStarImportForMembersSelector.value = settings.NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS
1 -> rbUseStarImports.setSelected(true)
else -> {
rbUseStarImportsIfMore.setSelected(true)
starImportLimitField.setValue(settings.NAME_COUNT_TO_USE_STAR_IMPORT - 1)
}
}
cbImportNestedClasses.setSelected(settings.IMPORT_NESTED_CLASSES) cbImportNestedClasses.setSelected(settings.IMPORT_NESTED_CLASSES)
@@ -144,11 +114,8 @@ class ImportSettingsPanel(private val commonSettings: CodeStyleSettings) : JPane
} }
fun apply(settings: JetCodeStyleSettings, dropEmptyPackages: Boolean = true) { fun apply(settings: JetCodeStyleSettings, dropEmptyPackages: Boolean = true) {
settings.NAME_COUNT_TO_USE_STAR_IMPORT = when { settings.NAME_COUNT_TO_USE_STAR_IMPORT = nameCountToUseStarImportSelector.value
rbUseSingleImports.isSelected() -> Int.MAX_VALUE settings.NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS = nameCountToUseStarImportForMembersSelector.value
rbUseStarImports.isSelected() -> 1
else -> (starImportLimitModel.getNumber() as Int) + 1
}
settings.IMPORT_NESTED_CLASSES = cbImportNestedClasses.isSelected() settings.IMPORT_NESTED_CLASSES = cbImportNestedClasses.isSelected()
if (dropEmptyPackages) { if (dropEmptyPackages) {
@@ -164,9 +131,58 @@ class ImportSettingsPanel(private val commonSettings: CodeStyleSettings) : JPane
tempSettings.writeExternal(root, settings) tempSettings.writeExternal(root, settings)
return root.getChildren().isNotEmpty() return root.getChildren().isNotEmpty()
} }
}
fun <T> T.init(initializer: T.() -> Unit): T { private class NameCountToUseStarImportSelector(label: String) : OptionGroup() {
initializer() private val rbUseSingleImports = JRadioButton("With single name")
return this private val rbUseStarImports = JRadioButton("With '*'")
private val rbUseStarImportsIfMore = JRadioButton("With '*' if more than ")
private val starImportLimitModel = SpinnerNumberModel(4, 1, 100, 1)
private val starImportLimitField = JSpinner(starImportLimitModel)
init {
ButtonGroup().apply {
add(rbUseSingleImports)
add(rbUseStarImports)
add(rbUseStarImportsIfMore)
}
add(JLabel(label))
add(rbUseSingleImports, true)
add(rbUseStarImports, true)
val jPanel: JPanel = JPanel(GridBagLayout())
add(jPanel.apply {
val constraints = GridBagConstraints().apply { gridx = GridBagConstraints.RELATIVE }
this.add(rbUseStarImportsIfMore, constraints)
this.add(starImportLimitField, constraints)
this.add(JLabel(" names used"), constraints.apply { fill = GridBagConstraints.HORIZONTAL; weightx = 1.0 })
}, true)
fun updateEnabled() {
starImportLimitField.setEnabled(rbUseStarImportsIfMore.isSelected())
}
rbUseStarImportsIfMore.addChangeListener { updateEnabled() }
updateEnabled()
}
var value: Int
get() {
return when {
rbUseSingleImports.isSelected -> Int.MAX_VALUE
rbUseStarImports.isSelected -> 1
else -> (starImportLimitModel.number as Int) + 1
}
}
set(value) {
when (value) {
Int.MAX_VALUE -> rbUseSingleImports.isSelected = true
1 -> rbUseStarImports.isSelected = true
else -> {
rbUseStarImportsIfMore.isSelected = true
starImportLimitField.value = value - 1
}
}
}
}
} }
@@ -164,15 +164,21 @@ public class KotlinImportOptimizer() : ImportOptimizer {
val importsToGenerate = HashSet<ImportPath>() val importsToGenerate = HashSet<ImportPath>()
val descriptorsByPackages = HashMultimap.create<FqName, DeclarationDescriptor>() val descriptorsByParentFqName = HashMultimap.create<FqName, DeclarationDescriptor>()
for (descriptor in descriptorsToImport) { for (descriptor in descriptorsToImport) {
val fqName = descriptor.importableFqName!! val fqName = descriptor.importableFqName!!
val container = descriptor.containingDeclaration
val parentFqName = fqName.parent() val parentFqName = fqName.parent()
if (descriptor is PackageViewDescriptor || parentFqName.isRoot()) { val canUseStarImport = when {
importsToGenerate.add(ImportPath(fqName, false)) parentFqName.isRoot -> false
(container as? ClassDescriptor)?.kind == ClassKind.OBJECT -> false
else -> true
}
if (canUseStarImport) {
descriptorsByParentFqName.put(parentFqName, descriptor)
} }
else { else {
descriptorsByPackages.put(parentFqName, descriptor) importsToGenerate.add(ImportPath(fqName, false))
} }
} }
@@ -180,11 +186,16 @@ public class KotlinImportOptimizer() : ImportOptimizer {
fun isImportedByDefault(fqName: FqName) = importInsertHelper.isImportedWithDefault(ImportPath(fqName, false), file) fun isImportedByDefault(fqName: FqName) = importInsertHelper.isImportedWithDefault(ImportPath(fqName, false), file)
for (packageName in descriptorsByPackages.keys()) { for (parentFqName in descriptorsByParentFqName.keys()) {
val descriptors = descriptorsByPackages[packageName] val descriptors = descriptorsByParentFqName[parentFqName]
val fqNames = descriptors.map { it.importableFqName!! }.toSet() val fqNames = descriptors.map { it.importableFqName!! }.toSet()
val explicitImports = fqNames.size() < codeStyleSettings.NAME_COUNT_TO_USE_STAR_IMPORT val isMember = descriptors.first().containingDeclaration is ClassDescriptor
&& packageName.asString() !in codeStyleSettings.PACKAGES_TO_USE_STAR_IMPORTS val nameCountToUseStar = if (isMember)
codeStyleSettings.NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS
else
codeStyleSettings.NAME_COUNT_TO_USE_STAR_IMPORT
val explicitImports = fqNames.size < nameCountToUseStar
&& parentFqName.asString() !in codeStyleSettings.PACKAGES_TO_USE_STAR_IMPORTS
if (explicitImports) { if (explicitImports) {
for (fqName in fqNames) { for (fqName in fqNames) {
if (!isImportedByDefault(fqName)) { if (!isImportedByDefault(fqName)) {
@@ -200,7 +211,7 @@ public class KotlinImportOptimizer() : ImportOptimizer {
} }
if (!fqNames.all(::isImportedByDefault)) { if (!fqNames.all(::isImportedByDefault)) {
importsToGenerate.add(ImportPath(packageName, true)) importsToGenerate.add(ImportPath(parentFqName, true))
} }
} }
} }
@@ -218,14 +229,14 @@ public class KotlinImportOptimizer() : ImportOptimizer {
// add explicit import if failed to import with * (or from current package) // add explicit import if failed to import with * (or from current package)
importsToGenerate.add(ImportPath(fqName, false)) importsToGenerate.add(ImportPath(fqName, false))
val packageName = fqName.parent() val parentFqName = fqName.parent()
for (descriptor in descriptorsByPackages[packageName].filter { it.importableFqName == fqName }) { for (descriptor in descriptorsByParentFqName[parentFqName].filter { it.importableFqName == fqName }) {
descriptorsByPackages.remove(packageName, descriptor) descriptorsByParentFqName.remove(parentFqName, descriptor)
} }
if (descriptorsByPackages[packageName].isEmpty()) { // star import is not really needed if (descriptorsByParentFqName[parentFqName].isEmpty()) { // star import is not really needed
importsToGenerate.remove(ImportPath(packageName, true)) importsToGenerate.remove(ImportPath(parentFqName, true))
} }
} }
} }
@@ -158,14 +158,13 @@ public class ImportInsertHelperImpl(private val project: Project) : ImportInsert
val fqName = target.importableFqName!! val fqName = target.importableFqName!!
val packageFqName = fqName.parent() val packageFqName = fqName.parent()
val tryStarImport = shouldTryStarImport(packageFqName, imports) val tryStarImport = shouldTryStarImport(packageFqName, target, imports)
&& when (target) { && when (target) {
// this check does not give a guarantee that import with * will import the class - for example, // 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 * // there can be classes with conflicting name in more than one import with *
is ClassDescriptor -> topLevelScope.getClassifier(name, NoLookupLocation.FROM_IDE) == null is ClassDescriptor -> topLevelScope.getClassifier(name, NoLookupLocation.FROM_IDE) == null
is PackageViewDescriptor -> false
is FunctionDescriptor, is PropertyDescriptor -> true is FunctionDescriptor, is PropertyDescriptor -> true
else -> throw Exception() else -> error("Unknown kind of descriptor to import:$target")
} }
if (tryStarImport) { if (tryStarImport) {
@@ -176,19 +175,26 @@ public class ImportInsertHelperImpl(private val project: Project) : ImportInsert
return addExplicitImport(target) return addExplicitImport(target)
} }
private fun shouldTryStarImport(packageFqName: FqName, imports: Collection<JetImportDirective>): Boolean { private fun shouldTryStarImport(containerFqName: FqName, target: DeclarationDescriptor, imports: Collection<JetImportDirective>): Boolean {
if (packageFqName.isRoot()) return false if (containerFqName.isRoot) return false
val starImportPath = ImportPath(packageFqName, true) val container = target.containingDeclaration
if (container is ClassDescriptor && container.kind == ClassKind.OBJECT) return false // cannot import with '*' from object
val starImportPath = ImportPath(containerFqName, true)
if (imports.any { it.getImportPath() == starImportPath }) return false if (imports.any { it.getImportPath() == starImportPath }) return false
if (packageFqName.asString() in codeStyleSettings.PACKAGES_TO_USE_STAR_IMPORTS) return true if (containerFqName.asString() in codeStyleSettings.PACKAGES_TO_USE_STAR_IMPORTS) return true
val importsFromPackage = imports.count { val importsFromPackage = imports.count {
val path = it.getImportPath() val path = it.getImportPath()
path != null && !path.isAllUnder() && !path.hasAlias() && path.fqnPart().parent() == packageFqName path != null && !path.isAllUnder() && !path.hasAlias() && path.fqnPart().parent() == containerFqName
} }
return importsFromPackage + 1 >= codeStyleSettings.NAME_COUNT_TO_USE_STAR_IMPORT val nameCountToUseStar = if (container is ClassDescriptor)
codeStyleSettings.NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS
else
codeStyleSettings.NAME_COUNT_TO_USE_STAR_IMPORT
return importsFromPackage + 1 >= nameCountToUseStar
} }
private fun addStarImport(target: DeclarationDescriptor): ImportDescriptorResult { private fun addStarImport(target: DeclarationDescriptor): ImportDescriptorResult {
@@ -0,0 +1,5 @@
package ppp
enum class E {
A, B, C
}
+1
View File
@@ -0,0 +1 @@
// IMPORT: ppp.E.A
+3
View File
@@ -0,0 +1,3 @@
import ppp.E.A
// IMPORT: ppp.E.A
@@ -0,0 +1,5 @@
package ppp
enum class E {
A, B, C
}
+3
View File
@@ -0,0 +1,3 @@
// IMPORT: ppp.E.C
import ppp.E.A
import ppp.E.B
+2
View File
@@ -0,0 +1,2 @@
// IMPORT: ppp.E.C
import ppp.E.*
+1
View File
@@ -1,4 +1,5 @@
// IMPORT: dependency.MyObject.someFun // IMPORT: dependency.MyObject.someFun
// NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS: 1
fun foo() { fun foo() {
someFun() someFun()
} }
+1
View File
@@ -1,6 +1,7 @@
import dependency.MyObject.someFun import dependency.MyObject.someFun
// IMPORT: dependency.MyObject.someFun // IMPORT: dependency.MyObject.someFun
// NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS: 1
fun foo() { fun foo() {
someFun() someFun()
} }
+1 -1
View File
@@ -1,3 +1,3 @@
import kotlin.Map.* import kotlin.Map.Entry
// IMPORT: kotlin.Map.Entry // IMPORT: kotlin.Map.Entry
@@ -0,0 +1,18 @@
package dependency
object O {
fun foo(){}
fun bar(){}
}
enum class E1 {
A1, B1, C1
}
enum class E2 {
A2, B2, C2
}
enum class E3 {
A3, B3, C3
}
@@ -0,0 +1,18 @@
// NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS: 2
package test
import dependency.O.foo
import dependency.O.bar
import dependency.E1.A1
import dependency.E1.B1
import dependency.E2.A2
import dependency.E3.*
fun f() {
foo()
bar()
val v1 = A1
val v2 = B1
val v3 = A2
val v4 = A3
}
@@ -0,0 +1,17 @@
// NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS: 2
package test
import dependency.E1.*
import dependency.E2.A2
import dependency.E3.A3
import dependency.O.bar
import dependency.O.foo
fun f() {
foo()
bar()
val v1 = A1
val v2 = B1
val v3 = A2
val v4 = A3
}
@@ -56,6 +56,7 @@ public abstract class AbstractImportsTest : JetLightCodeInsightFixtureTestCase()
val fileText = file.getText() val fileText = file.getText()
codeStyleSettings.NAME_COUNT_TO_USE_STAR_IMPORT = InTextDirectivesUtils.getPrefixedInt(fileText, "// NAME_COUNT_TO_USE_STAR_IMPORT:") ?: nameCountToUseStarImportDefault codeStyleSettings.NAME_COUNT_TO_USE_STAR_IMPORT = InTextDirectivesUtils.getPrefixedInt(fileText, "// NAME_COUNT_TO_USE_STAR_IMPORT:") ?: nameCountToUseStarImportDefault
codeStyleSettings.NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS = InTextDirectivesUtils.getPrefixedInt(fileText, "// NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS:") ?: nameCountToUseStarImportForMembersDefault
codeStyleSettings.IMPORT_NESTED_CLASSES = InTextDirectivesUtils.getPrefixedBoolean(fileText, "// IMPORT_NESTED_CLASSES:") ?: false codeStyleSettings.IMPORT_NESTED_CLASSES = InTextDirectivesUtils.getPrefixedBoolean(fileText, "// IMPORT_NESTED_CLASSES:") ?: false
InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileText, "// PACKAGE_TO_USE_STAR_IMPORTS:").forEach { InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileText, "// PACKAGE_TO_USE_STAR_IMPORTS:").forEach {
@@ -80,4 +81,7 @@ public abstract class AbstractImportsTest : JetLightCodeInsightFixtureTestCase()
protected open val nameCountToUseStarImportDefault: Int protected open val nameCountToUseStarImportDefault: Int
get() = 1 get() = 1
protected open val nameCountToUseStarImportForMembersDefault: Int
get() = 3
} }
@@ -191,6 +191,18 @@ public class AddImportTestGenerated extends AbstractAddImportTest {
doTest(fileName); doTest(fileName);
} }
@TestMetadata("ImportEnumMember1.kt")
public void testImportEnumMember1() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/addImport/ImportEnumMember1.kt");
doTest(fileName);
}
@TestMetadata("ImportEnumMember2.kt")
public void testImportEnumMember2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/addImport/ImportEnumMember2.kt");
doTest(fileName);
}
@TestMetadata("ImportFromObject.kt") @TestMetadata("ImportFromObject.kt")
public void testImportFromObject() throws Exception { public void testImportFromObject() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/addImport/ImportFromObject.kt"); String fileName = JetTestUtils.navigationMetadata("idea/testData/addImport/ImportFromObject.kt");
@@ -259,6 +259,12 @@ public class OptimizeImportsTestGenerated extends AbstractOptimizeImportsTest {
doTest(fileName); doTest(fileName);
} }
@TestMetadata("MemberImports.kt")
public void testMemberImports() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/editor/optimizeImports/allUnderImports/MemberImports.kt");
doTest(fileName);
}
@TestMetadata("NameCountSetting.kt") @TestMetadata("NameCountSetting.kt")
public void testNameCountSetting() throws Exception { public void testNameCountSetting() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/editor/optimizeImports/allUnderImports/NameCountSetting.kt"); String fileName = JetTestUtils.navigationMetadata("idea/testData/editor/optimizeImports/allUnderImports/NameCountSetting.kt");