Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

KT-10974 Add Code Style: Import Layout Configuration Table #3336

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@
import com.intellij.openapi.util.WriteExternalException;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.CustomCodeStyleSettings;
import com.intellij.psi.codeStyle.PackageEntry;
import com.intellij.psi.codeStyle.PackageEntryTable;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
Expand All @@ -26,7 +24,8 @@
import static com.intellij.util.ReflectionUtil.copyFields;

public class KotlinCodeStyleSettings extends CustomCodeStyleSettings {
public final PackageEntryTable PACKAGES_TO_USE_STAR_IMPORTS = new PackageEntryTable();
public final KotlinPackageEntryTable PACKAGES_TO_USE_STAR_IMPORTS = new KotlinPackageEntryTable();
public final KotlinPackageEntryTable PACKAGES_IMPORT_LAYOUT = new KotlinPackageEntryTable();
public boolean SPACE_AROUND_RANGE = false;
public boolean SPACE_BEFORE_TYPE_COLON = false;
public boolean SPACE_AFTER_TYPE_COLON = true;
Expand Down Expand Up @@ -77,8 +76,14 @@ private KotlinCodeStyleSettings(CodeStyleSettings container, boolean isTempForDe

// defaults in IDE but not in tests
if (!ApplicationManager.getApplication().isUnitTestMode()) {
PACKAGES_TO_USE_STAR_IMPORTS.addEntry(new PackageEntry(false, "java.util", false));
PACKAGES_TO_USE_STAR_IMPORTS.addEntry(new PackageEntry(false, "kotlinx.android.synthetic", true));
PACKAGES_TO_USE_STAR_IMPORTS.addEntry(new KotlinPackageEntry("java.util", false));
PACKAGES_TO_USE_STAR_IMPORTS.addEntry(new KotlinPackageEntry("kotlinx.android.synthetic", true));

PACKAGES_IMPORT_LAYOUT.addEntry(KotlinPackageEntry.ALL_OTHER_IMPORTS_ENTRY);
PACKAGES_IMPORT_LAYOUT.addEntry(new KotlinPackageEntry("java", true));
PACKAGES_IMPORT_LAYOUT.addEntry(new KotlinPackageEntry("javax", true));
PACKAGES_IMPORT_LAYOUT.addEntry(new KotlinPackageEntry("kotlin", true));
PACKAGES_IMPORT_LAYOUT.addEntry(KotlinPackageEntry.ALL_OTHER_ALIAS_IMPORTS_ENTRY);
}
}

Expand All @@ -103,6 +108,7 @@ public KotlinCodeStyleSettings cloneSettings() {
private void copyFrom(@NotNull KotlinCodeStyleSettings from) {
copyFields(getClass().getFields(), from, this);
PACKAGES_TO_USE_STAR_IMPORTS.copyFrom(from.PACKAGES_TO_USE_STAR_IMPORTS);
PACKAGES_IMPORT_LAYOUT.copyFrom(from.PACKAGES_IMPORT_LAYOUT);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/

package org.jetbrains.kotlin.idea.core.formatter

import com.intellij.openapi.application.ApplicationBundle
import org.jetbrains.kotlin.resolve.ImportPath

class KotlinPackageEntry(
packageName: String,
val withSubpackages: Boolean
) {
val packageName = packageName.removeSuffix(".*")

companion object {
@JvmField
val ALL_OTHER_IMPORTS_ENTRY =
KotlinPackageEntry(ApplicationBundle.message("listbox.import.all.other.imports"), withSubpackages = true)

@JvmField
val ALL_OTHER_ALIAS_IMPORTS_ENTRY = KotlinPackageEntry("<all other alias imports>", withSubpackages = true)
}

fun matchesPackageName(otherPackageName: String): Boolean {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function can be private

if (isSpecial) return true

if (otherPackageName.startsWith(packageName)) {
if (otherPackageName.length == packageName.length) return true
if (withSubpackages) {
if (otherPackageName[packageName.length] == '.') return true
}
}
return false
}

fun isBetterMatchForPackageThan(entry: KotlinPackageEntry?, path: ImportPath): Boolean {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that this KotlinPackageEntry? is necessary here, you can just check for null before passing it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fedochet I don't think that would work

I would have to extract call to matchesPackageName as well otherwise we would end up selecting incorrect package in method bestMatchIndex

if (!matchesPackageName(path.pathStr)) return false
if (entry == null) return true

if (this == ALL_OTHER_ALIAS_IMPORTS_ENTRY && path.hasAlias()) return true
if (entry == ALL_OTHER_ALIAS_IMPORTS_ENTRY && path.hasAlias()) return false

if (entry == ALL_OTHER_IMPORTS_ENTRY && this != ALL_OTHER_ALIAS_IMPORTS_ENTRY) return true
if (this == ALL_OTHER_IMPORTS_ENTRY && entry != ALL_OTHER_ALIAS_IMPORTS_ENTRY) return false
if (entry.withSubpackages != withSubpackages) return !withSubpackages

return entry.packageName.count { it == '.' } < packageName.count { it == '.' }
}

val isSpecial: Boolean get() = this == ALL_OTHER_IMPORTS_ENTRY || this == ALL_OTHER_ALIAS_IMPORTS_ENTRY

override fun toString(): String {
return packageName
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/

package org.jetbrains.kotlin.idea.core.formatter

import com.intellij.openapi.util.InvalidDataException
import com.intellij.openapi.util.JDOMExternalizable
import org.jdom.Element

class KotlinPackageEntryTable : JDOMExternalizable, Cloneable {
private val entries = mutableListOf<KotlinPackageEntry>()

val entryCount: Int get() = entries.size

public override fun clone(): KotlinPackageEntryTable {
val clone = KotlinPackageEntryTable()
clone.copyFrom(this)
return clone
}

fun copyFrom(packageTable: KotlinPackageEntryTable) {
entries.clear()
entries.addAll(packageTable.entries)
}

fun getEntries(): Array<KotlinPackageEntry> {
return entries.toTypedArray()
}

fun insertEntryAt(entry: KotlinPackageEntry, index: Int) {
entries.add(index, entry)
}

fun removeEntryAt(index: Int) {
entries.removeAt(index)
}

fun getEntryAt(index: Int): KotlinPackageEntry {
return entries[index]
}

fun setEntryAt(entry: KotlinPackageEntry, index: Int) {
entries[index] = entry
}

operator fun contains(packageName: String): Boolean {
return entries.any { !it.isSpecial && it.matchesPackageName(packageName) }
}

fun removeEmptyPackages() {
entries.removeAll { it.packageName.isBlank() }
}

fun addEntry(entry: KotlinPackageEntry) {
entries.add(entry)
}

override fun readExternal(element: Element) {
entries.clear()

element.children.forEach {
if (it.name == "package") {
val packageName = it.getAttributeValue("name") ?: throw InvalidDataException()
val alias = it.getAttributeValue("alias")?.toBoolean() ?: false
val withSubpackages = it.getAttributeValue("withSubpackages")?.toBoolean() ?: false

val entry = when {
packageName.isEmpty() && !alias -> KotlinPackageEntry.ALL_OTHER_IMPORTS_ENTRY
packageName.isEmpty() && alias -> KotlinPackageEntry.ALL_OTHER_ALIAS_IMPORTS_ENTRY
else -> KotlinPackageEntry(packageName, withSubpackages)
}

entries.add(entry)
}
}
}

override fun writeExternal(parentNode: Element) {
for (entry in entries) {
val element = Element("package")
parentNode.addContent(element)
val name = if (entry.isSpecial) "" else entry.packageName
val alias = (entry == KotlinPackageEntry.ALL_OTHER_ALIAS_IMPORTS_ENTRY)

element.setAttribute("name", name)
element.setAttribute("alias", alias.toString())
element.setAttribute("withSubpackages", entry.withSubpackages.toString())
}
}
}
24 changes: 0 additions & 24 deletions idea/ide-common/src/org/jetbrains/kotlin/idea/util/ImportsUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,36 +27,12 @@ import org.jetbrains.kotlin.psi.KtSimpleNameExpression
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedElementSelector
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.ImportPath
import org.jetbrains.kotlin.resolve.bindingContextUtil.getReferenceTargets
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.getImportableDescriptor
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.KotlinType

object ImportPathComparator : Comparator<ImportPath> {
override fun compare(import1: ImportPath, import2: ImportPath): Int {
// alias imports placed last
if (import1.hasAlias() != import2.hasAlias()) {
return if (import1.hasAlias()) +1 else -1
}

// standard library imports last
val stdlib1 = isJavaOrKotlinStdlibImport(import1)
val stdlib2 = isJavaOrKotlinStdlibImport(import2)
if (stdlib1 != stdlib2) {
return if (stdlib1) +1 else -1
}

return import1.toString().compareTo(import2.toString())
}

private fun isJavaOrKotlinStdlibImport(path: ImportPath): Boolean {
val s = path.pathStr
return s.startsWith("java.") || s.startsWith("javax.") || s.startsWith("kotlin.")
}
}

val DeclarationDescriptor.importableFqName: FqName?
get() {
if (!canBeReferencedViaImport()) return null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import junit.framework.TestCase
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.idea.core.formatter.KotlinCodeStyleSettings
import org.jetbrains.kotlin.idea.core.formatter.KotlinPackageEntry
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
import org.jetbrains.kotlin.idea.util.ImportInsertHelper
Expand Down Expand Up @@ -61,10 +62,10 @@ abstract class AbstractPerformanceImportTest : KotlinLightCodeInsightFixtureTest
InTextDirectivesUtils.getPrefixedBoolean(fileText, "// IMPORT_NESTED_CLASSES:") ?: false

InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileText, "// PACKAGE_TO_USE_STAR_IMPORTS:").forEach {
codeStyleSettings.PACKAGES_TO_USE_STAR_IMPORTS.addEntry(PackageEntry(false, it.trim(), false))
codeStyleSettings.PACKAGES_TO_USE_STAR_IMPORTS.addEntry(KotlinPackageEntry(it.trim(), false))
}
InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileText, "// PACKAGES_TO_USE_STAR_IMPORTS:").forEach {
codeStyleSettings.PACKAGES_TO_USE_STAR_IMPORTS.addEntry(PackageEntry(false, it.trim(), true))
codeStyleSettings.PACKAGES_TO_USE_STAR_IMPORTS.addEntry(KotlinPackageEntry(it.trim(), true))
}

var descriptorName = InTextDirectivesUtils.findStringWithPrefixes(file.text, "// IMPORT:")
Expand Down
3 changes: 2 additions & 1 deletion idea/resources/messages/KotlinBundle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2206,4 +2206,5 @@ kotlin.script.definitions.model.name.name=Name
codestyle.name.kotlin=Kotlin
add.missing.class.keyword=Add missing 'class' keyword
fix.move.typealias.to.top.level=Move typealias to top level
fix.change.jvm.name=Change JVM name
fix.change.jvm.name=Change JVM name
codestyle.layout.import.aliases.separately=Import aliases separately
Loading