-
Notifications
You must be signed in to change notification settings - Fork 506
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce import layout configuration with predefined layouts for Imp…
…ortOrderingRule
- Loading branch information
Showing
13 changed files
with
969 additions
and
104 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
140 changes: 129 additions & 11 deletions
140
...eset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/ImportOrderingRule.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
44 changes: 44 additions & 0 deletions
44
...otlin/com/pinterest/ktlint/ruleset/standard/internal/importordering/ImportLayoutParser.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package com.pinterest.ktlint.ruleset.standard.internal.importordering | ||
|
||
internal const val BLANK_LINE_CHAR = "|" | ||
internal const val WILDCARD_CHAR = "*" | ||
internal const val ALIAS_CHAR = "^" // TODO: replace with a proper char, once implemented on IDEA's side | ||
|
||
/** | ||
* Adopted from https://github.com/JetBrains/intellij-community/blob/70fd799e94246f2c0fe924763ed892765c0dff9a/java/java-impl/src/com/intellij/psi/codeStyle/JavaPackageEntryTableAccessor.java#L25 | ||
*/ | ||
internal fun parseImportsLayout(importsLayout: String): List<PatternEntry> { | ||
val imports = mutableListOf<PatternEntry>() | ||
val importsList = importsLayout.split(",").onEach { it.trim() } | ||
|
||
if (importsList.first() == BLANK_LINE_CHAR || importsList.last() == BLANK_LINE_CHAR) { | ||
throw IllegalArgumentException("Blank lines are not supported in the beginning or end of import list") | ||
} | ||
|
||
if (WILDCARD_CHAR !in importsList) { | ||
throw IllegalArgumentException("<all other imports> symbol (\"*\") must be present in the custom imports layout") | ||
} | ||
|
||
importsList.forEach { | ||
var import = it | ||
if (import == BLANK_LINE_CHAR) { | ||
imports += PatternEntry.BLANK_LINE_ENTRY | ||
} else { | ||
var hasAlias = false | ||
var withSubpackages = false | ||
if (import.startsWith(ALIAS_CHAR)) { | ||
import = import.substring(1).trim() | ||
hasAlias = true | ||
} | ||
if (import.endsWith(WILDCARD_CHAR)) { | ||
withSubpackages = true | ||
} | ||
imports += if (import == WILDCARD_CHAR) { | ||
if (hasAlias) PatternEntry.ALL_OTHER_ALIAS_IMPORTS_ENTRY else PatternEntry.ALL_OTHER_IMPORTS_ENTRY | ||
} else { | ||
PatternEntry(import, withSubpackages, hasAlias) | ||
} | ||
} | ||
} | ||
return imports | ||
} |
52 changes: 52 additions & 0 deletions
52
...main/kotlin/com/pinterest/ktlint/ruleset/standard/internal/importordering/ImportSorter.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package com.pinterest.ktlint.ruleset.standard.internal.importordering | ||
|
||
import org.jetbrains.kotlin.psi.KtImportDirective | ||
import org.jetbrains.kotlin.resolve.ImportPath | ||
|
||
/** | ||
* Sorts the imports according to the order specified in [patterns] + alphabetically. | ||
* | ||
* Adopted from https://github.com/JetBrains/kotlin/blob/a270ee094c4d7b9520e0898a242bb6ce4dfcad7b/idea/src/org/jetbrains/kotlin/idea/util/ImportPathComparator.kt#L15 | ||
*/ | ||
internal class ImportSorter(importsLayout: String) : Comparator<KtImportDirective> { | ||
|
||
val patterns: List<PatternEntry> = parseImportsLayout(importsLayout) | ||
|
||
override fun compare(import1: KtImportDirective, import2: KtImportDirective): Int { | ||
val importPath1 = import1.importPath!! | ||
val importPath2 = import2.importPath!! | ||
|
||
return compareValuesBy( | ||
importPath1, | ||
importPath2, | ||
{ import -> findImportIndex(import) }, | ||
{ import -> import.toString() } | ||
) | ||
} | ||
|
||
fun findImportIndex(path: ImportPath): Int { | ||
var bestIndex: Int = -1 | ||
var bestEntryMatch: PatternEntry? = null | ||
var allOtherAliasIndex = -1 | ||
var allOtherIndex = -1 | ||
|
||
for ((index, entry) in patterns.withIndex()) { | ||
if (entry == PatternEntry.ALL_OTHER_ALIAS_IMPORTS_ENTRY) { | ||
allOtherAliasIndex = index | ||
} | ||
if (entry == PatternEntry.ALL_OTHER_IMPORTS_ENTRY) { | ||
allOtherIndex = index | ||
} | ||
if (entry.isBetterMatchForPackageThan(bestEntryMatch, path)) { | ||
bestEntryMatch = entry | ||
bestIndex = index | ||
} | ||
} | ||
|
||
if (bestIndex == -1 && path.hasAlias() && allOtherAliasIndex == -1 && allOtherIndex != -1) { | ||
// if no layout for alias imports specified, put them among all others | ||
bestIndex = allOtherIndex | ||
} | ||
return bestIndex | ||
} | ||
} |
72 changes: 72 additions & 0 deletions
72
...main/kotlin/com/pinterest/ktlint/ruleset/standard/internal/importordering/PatternEntry.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
package com.pinterest.ktlint.ruleset.standard.internal.importordering | ||
|
||
import org.jetbrains.kotlin.resolve.ImportPath | ||
|
||
/** | ||
* Represents an entry in the imports layout pattern. Contains matching logic for imports. | ||
* | ||
* Adopted from https://github.com/JetBrains/kotlin/blob/ffdab473e28d0d872136b910eb2e0f4beea2e19c/idea/formatter/src/org/jetbrains/kotlin/idea/core/formatter/KotlinPackageEntry.kt#L10 | ||
*/ | ||
internal class PatternEntry( | ||
packageName: String, | ||
val withSubpackages: Boolean, | ||
val hasAlias: Boolean | ||
) { | ||
|
||
private val packageName = packageName.removeSuffix(".*") | ||
|
||
private fun matchesPackageName(otherPackageName: String): Boolean { | ||
if (this == ALL_OTHER_IMPORTS_ENTRY || this == ALL_OTHER_ALIAS_IMPORTS_ENTRY) return true | ||
if (this == BLANK_LINE_ENTRY) return false | ||
|
||
if (otherPackageName.startsWith(packageName)) { | ||
if (otherPackageName.length == packageName.length) return true | ||
if (withSubpackages) { | ||
if (otherPackageName[packageName.length] == '.') return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
fun isBetterMatchForPackageThan(entry: PatternEntry?, import: ImportPath): Boolean { | ||
if (hasAlias != import.hasAlias() || !matchesPackageName(import.pathStr)) return false | ||
if (entry == null) return true | ||
|
||
if (entry.hasAlias != hasAlias) return false | ||
// Any matched package is better than ALL_OTHER_IMPORTS_ENTRY | ||
if (this == ALL_OTHER_IMPORTS_ENTRY) return false | ||
if (entry == ALL_OTHER_IMPORTS_ENTRY) return true | ||
|
||
if (entry.withSubpackages != withSubpackages) return !withSubpackages | ||
|
||
return entry.packageName.count { it == '.' } < packageName.count { it == '.' } | ||
} | ||
|
||
override fun toString(): String = packageName | ||
|
||
override fun equals(other: Any?): Boolean { | ||
if (this === other) return true | ||
if (javaClass != other?.javaClass) return false | ||
|
||
other as PatternEntry | ||
|
||
if (withSubpackages != other.withSubpackages) return false | ||
if (hasAlias != other.hasAlias) return false | ||
if (packageName != other.packageName) return false | ||
|
||
return true | ||
} | ||
|
||
override fun hashCode(): Int { | ||
var result = withSubpackages.hashCode() | ||
result = 31 * result + hasAlias.hashCode() | ||
result = 31 * result + packageName.hashCode() | ||
return result | ||
} | ||
|
||
companion object { | ||
val BLANK_LINE_ENTRY = PatternEntry(BLANK_LINE_CHAR, withSubpackages = true, hasAlias = false) | ||
val ALL_OTHER_IMPORTS_ENTRY = PatternEntry(WILDCARD_CHAR, withSubpackages = true, hasAlias = false) | ||
val ALL_OTHER_ALIAS_IMPORTS_ENTRY = PatternEntry((ALIAS_CHAR + WILDCARD_CHAR), withSubpackages = true, hasAlias = true) | ||
} | ||
} |
Oops, something went wrong.