Skip to content

Commit

Permalink
Merge pull request #935 from Tapchicoma/701/import-ordering
Browse files Browse the repository at this point in the history
Add editorconfig value generation support to 'import-ordering' rule.
  • Loading branch information
Tapchicoma authored Oct 7, 2020
2 parents d818277 + 9a885c0 commit 69cc0f7
Show file tree
Hide file tree
Showing 6 changed files with 401 additions and 104 deletions.
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).

### Added
- Initial implementation IDE integration via '.editorconfig' based on rules default values ([#701](https://github.com/pinterest/ktlint/issues/701))
- CLI subcommand `generateEditorConfig` to generate '.editorconfig' content for Kotlin files ([#701](https://github.com/pinterest/ktlint/issues/701))
- CLI subcommand `generateEditorConfig` to generate '.editorconfig' content for Kotlin files ([#701](https://github.com/pinterest/ktlint/issues/701))

### Fixed
- Do not report when semicolon is before annotation/comment/kdoc and lambda ([#825](https://github.com/pinterest/ktlint/issues/825))
Expand All @@ -20,7 +20,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Fix false positive for method after string template in `argument-list-wrapping` ([#842](https://github.com/pinterest/ktlint/issues/842)) ([#859](https://github.com/pinterest/ktlint/issues/859))

### Changed
- ?
- 'import-ordering' now supports `.editorconfig' default value generation ([#701](https://github.com/pinterest/ktlint/issues/701))

### Removed
- ?
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
package com.pinterest.ktlint.ruleset.standard

import com.pinterest.ktlint.core.EditorConfig
import com.pinterest.ktlint.core.KtLint
import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.api.EditorConfigProperties
import com.pinterest.ktlint.core.api.FeatureInAlphaState
import com.pinterest.ktlint.core.api.UsesEditorConfigProperties
import com.pinterest.ktlint.core.ast.ElementType
import com.pinterest.ktlint.core.ast.isRoot
import com.pinterest.ktlint.ruleset.standard.ImportOrderingRule.Companion.ASCII_PATTERN
import com.pinterest.ktlint.ruleset.standard.ImportOrderingRule.Companion.IDEA_PATTERN
import com.pinterest.ktlint.ruleset.standard.internal.importordering.ImportSorter
import com.pinterest.ktlint.ruleset.standard.internal.importordering.PatternEntry
import com.pinterest.ktlint.ruleset.standard.internal.importordering.parseImportsLayout
import org.ec4j.core.model.PropertyType
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl
Expand All @@ -31,20 +35,27 @@ import org.jetbrains.kotlin.psi.KtImportDirective
*
* In case the custom property is not provided, the rule defaults to "ascii" style in case of "android" flag supplied, or to "idea" otherwise.
*/
class ImportOrderingRule : Rule("import-ordering") {
@OptIn(FeatureInAlphaState::class)
public class ImportOrderingRule :
Rule("import-ordering"),
UsesEditorConfigProperties {

private lateinit var importsLayout: String
private lateinit var importsLayout: List<PatternEntry>
private lateinit var importSorter: ImportSorter

companion object {
public companion object {
internal const val KTLINT_CUSTOM_IMPORTS_LAYOUT_PROPERTY_NAME = "kotlin_imports_layout"
internal const val IDEA_IMPORTS_LAYOUT_PROPERTY_NAME = "ij_kotlin_imports_layout"
private const val PROPERTY_DESCRIPTION = "Defines imports order layout for Kotlin files"

/**
* Alphabetical with capital letters before lower case letters (e.g. Z before a).
* No blank lines between major groups (android, com, junit, net, org, java, javax).
* Single group regardless of import type.
*
* https://developer.android.com/kotlin/style-guide#import_statements
*/
private const val ASCII_PATTERN = "*"
private val ASCII_PATTERN = parseImportsLayout("*")

/**
* Default IntelliJ IDEA style: Alphabetical with capital letters before lower case letters (e.g. Z before a),
Expand All @@ -54,7 +65,7 @@ class ImportOrderingRule : Rule("import-ordering") {
*
* https://github.com/JetBrains/kotlin/blob/ffdab473e28d0d872136b910eb2e0f4beea2e19c/idea/formatter/src/org/jetbrains/kotlin/idea/core/formatter/KotlinCodeStyleSettings.java#L87-L91
*/
private const val IDEA_PATTERN = "*,java.*,javax.*,kotlin.*,^*"
private val IDEA_PATTERN = parseImportsLayout("*,java.*,javax.*,kotlin.*,^*")

private const val IDEA_ERROR_MESSAGE = "Imports must be ordered in lexicographic order without any empty lines in-between " +
"with \"java\", \"javax\", \"kotlin\" and aliases in the end"
Expand All @@ -65,16 +76,76 @@ class ImportOrderingRule : Rule("import-ordering") {
IDEA_PATTERN to IDEA_ERROR_MESSAGE,
ASCII_PATTERN to ASCII_ERROR_MESSAGE
)

private val editorConfigPropertyParser: (String, String?) -> PropertyType.PropertyValue<List<PatternEntry>> =
{ _, value ->
when {
value == null -> PropertyType.PropertyValue.invalid(
value,
"Null is not supported for import layout"
)
value.isBlank() -> PropertyType.PropertyValue.valid(
value,
emptyList()
)
value == "idea" -> PropertyType.PropertyValue.valid(
value,
IDEA_PATTERN
)
value == "ascii" -> PropertyType.PropertyValue.valid(
value,
ASCII_PATTERN
)
else -> try {
PropertyType.PropertyValue.valid(
value,
parseImportsLayout(value)
)
} catch (e: IllegalArgumentException) {
PropertyType.PropertyValue.invalid(
value,
"Unexpected imports layout: $value"
)
}
}
}

internal val ktlintCustomImportsLayoutProperty =
UsesEditorConfigProperties.EditorConfigProperty<List<PatternEntry>>(
type = PropertyType(
KTLINT_CUSTOM_IMPORTS_LAYOUT_PROPERTY_NAME,
PROPERTY_DESCRIPTION,
editorConfigPropertyParser
),
defaultValue = IDEA_PATTERN,
defaultAndroidValue = ASCII_PATTERN
)

internal val ideaImportsLayoutProperty =
UsesEditorConfigProperties.EditorConfigProperty<List<PatternEntry>>(
type = PropertyType(
IDEA_IMPORTS_LAYOUT_PROPERTY_NAME,
PROPERTY_DESCRIPTION,
editorConfigPropertyParser
),
defaultValue = IDEA_PATTERN,
defaultAndroidValue = ASCII_PATTERN
)
}

override val editorConfigProperties: List<UsesEditorConfigProperties.EditorConfigProperty<*>> = listOf(
ktlintCustomImportsLayoutProperty,
ideaImportsLayoutProperty,
)

override fun visit(
node: ASTNode,
autoCorrect: Boolean,
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
) {
if (node.isRoot()) {
val android = node.getUserData(KtLint.ANDROID_USER_DATA_KEY) ?: false
val editorConfig = node.getUserData(KtLint.EDITOR_CONFIG_USER_DATA_KEY)!!
val editorConfig = node.getUserData(KtLint.EDITOR_CONFIG_PROPERTIES_USER_DATA_KEY)!!
importsLayout = editorConfig.resolveImportsLayout(android)
importSorter = ImportSorter(importsLayout)
return
Expand Down Expand Up @@ -147,19 +218,15 @@ class ImportOrderingRule : Rule("import-ordering") {
}
}

private fun EditorConfig.resolveImportsLayout(android: Boolean): String {
val defaultPattern = if (android) "ascii" else "idea"
val layout = when {
!get("kotlin_imports_layout").isNullOrEmpty() -> get("kotlin_imports_layout")!!
!get("ij_kotlin_imports_layout").isNullOrEmpty() -> get("ij_kotlin_imports_layout")!!
else -> defaultPattern // default to ascii (android) or idea in case there's no such entry in .editorconfig
}

return when (layout) { // transform predefined styles into patterns
"idea" -> IDEA_PATTERN
"ascii" -> ASCII_PATTERN
else -> layout
}
private fun EditorConfigProperties.resolveImportsLayout(
android: Boolean
): List<PatternEntry> = when {
containsKey(KTLINT_CUSTOM_IMPORTS_LAYOUT_PROPERTY_NAME) ->
getValue(KTLINT_CUSTOM_IMPORTS_LAYOUT_PROPERTY_NAME).getValueAs()
containsKey(IDEA_IMPORTS_LAYOUT_PROPERTY_NAME) ->
getValue(IDEA_IMPORTS_LAYOUT_PROPERTY_NAME).getValueAs()
else ->
if (android) ideaImportsLayoutProperty.defaultAndroidValue else ideaImportsLayoutProperty.defaultValue
}

private fun importsAreEqual(actual: List<ASTNode>, expected: List<ASTNode>): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import org.jetbrains.kotlin.resolve.ImportPath
*
* 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)
internal class ImportSorter(
val patterns: List<PatternEntry>
) : Comparator<KtImportDirective> {

override fun compare(import1: KtImportDirective, import2: KtImportDirective): Int {
val importPath1 = import1.importPath!!
Expand Down
Loading

0 comments on commit 69cc0f7

Please # to comment.