Skip to content

Commit

Permalink
KT-10974 - add import layout configuration table
Browse files Browse the repository at this point in the history
  • Loading branch information
gcx11 committed Apr 25, 2020
1 parent 38622d8 commit f688786
Show file tree
Hide file tree
Showing 10 changed files with 581 additions and 56 deletions.
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,55 @@
/*
* 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 org.jetbrains.kotlin.resolve.ImportPath

data class KotlinPackageEntry(
val packageName: String,
val withSubpackages: Boolean
) {
companion object {
@JvmField
val ALL_OTHER_IMPORTS_ENTRY = KotlinPackageEntry("<all other imports>", withSubpackages = true)

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

fun matchesPackageName(otherPackageName: String): Boolean {
if (this == ALL_OTHER_IMPORTS_ENTRY || this == ALL_OTHER_ALIAS_IMPORTS_ENTRY) 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 {
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() {
return (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,113 @@
/*
* 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>()

override fun equals(other: Any?): Boolean {
if (other !is KotlinPackageEntryTable) return false
if (other.entries.size != entries.size) return false

return entries.zip(other.entries).any { (entry, otherEntry) -> entry != otherEntry }
}

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

override fun hashCode(): Int {
return entries.firstOrNull()?.hashCode() ?: 0
}

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 getEntryCount(): Int {
return entries.size
}

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

operator fun contains(packageName: String): Boolean {
for (entry in entries) {
if (packageName.startsWith(entry.packageName)) {
if (packageName.length == entry.packageName.length) return true
if (entry.withSubpackages) {
if (packageName[entry.packageName.length] == '.') return true
}
}
}
return false
}

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 == KotlinPackageEntry.ALL_OTHER_IMPORTS_ENTRY || entry == KotlinPackageEntry.ALL_OTHER_ALIAS_IMPORTS_ENTRY) "" 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
Loading

0 comments on commit f688786

Please sign in to comment.