Skip to content

Commit

Permalink
Refactor usages of "ASTNode.getEditorConfigValue" to "EditorConfigPro…
Browse files Browse the repository at this point in the history
…perties.getEditorConfigValue" by using the Rule "beforeFirstNode" lifecycle hook
  • Loading branch information
paul-dingemans committed Jul 17, 2022
1 parent 53ccf79 commit beeaf55
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 153 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import org.jetbrains.kotlin.utils.addToStdlib.safeAs

public object KtLint {
public val FILE_PATH_USER_DATA_KEY: Key<String> = Key<String>("FILE_PATH")

@Deprecated("Marked for removal in Ktlint 0.48.0")
public val EDITOR_CONFIG_PROPERTIES_USER_DATA_KEY: Key<EditorConfigProperties> =
Key<EditorConfigProperties>("EDITOR_CONFIG_PROPERTIES")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.api.DefaultEditorConfigProperties.indentSizeProperty
import com.pinterest.ktlint.core.api.DefaultEditorConfigProperties.indentStyleProperty
import com.pinterest.ktlint.core.api.DefaultEditorConfigProperties.maxLineLengthProperty
import com.pinterest.ktlint.core.api.EditorConfigProperties
import com.pinterest.ktlint.core.api.UsesEditorConfigProperties
import com.pinterest.ktlint.core.ast.ElementType.ANNOTATION_ENTRY
import com.pinterest.ktlint.core.ast.ElementType.BLOCK
Expand All @@ -20,7 +21,6 @@ import com.pinterest.ktlint.core.ast.ElementType.VALUE_PARAMETER
import com.pinterest.ktlint.core.ast.ElementType.VALUE_PARAMETER_LIST
import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE
import com.pinterest.ktlint.core.ast.children
import com.pinterest.ktlint.core.ast.isRoot
import com.pinterest.ktlint.core.ast.isWhiteSpace
import com.pinterest.ktlint.core.ast.isWhiteSpaceWithNewline
import com.pinterest.ktlint.core.ast.lineIndent
Expand Down Expand Up @@ -66,26 +66,24 @@ public class FunctionSignatureRule :
private var functionSignatureWrappingMinimumParameters = -1
private var functionBodyExpressionWrapping = default

override fun beforeVisitChildNodes(
node: ASTNode,
autoCorrect: Boolean,
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
) {
if (node.isRoot()) {
functionSignatureWrappingMinimumParameters = node.getEditorConfigValue(forceMultilineWhenParameterCountGreaterOrEqualThanProperty)
functionBodyExpressionWrapping = node.getEditorConfigValue(functionBodyExpressionWrappingProperty)
override fun beforeFirstNode(editorConfigProperties: EditorConfigProperties) {
with(editorConfigProperties) {
functionSignatureWrappingMinimumParameters = getEditorConfigValue(forceMultilineWhenParameterCountGreaterOrEqualThanProperty)
functionBodyExpressionWrapping = getEditorConfigValue(functionBodyExpressionWrappingProperty)
val indentConfig = IndentConfig(
indentStyle = node.getEditorConfigValue(indentStyleProperty),
tabWidth = node.getEditorConfigValue(indentSizeProperty)
indentStyle = getEditorConfigValue(indentStyleProperty),
tabWidth = getEditorConfigValue(indentSizeProperty)
)
if (indentConfig.disabled) {
return
}
indent = indentConfig.indent
maxLineLength = node.getEditorConfigValue(maxLineLengthProperty)
return
maxLineLength = getEditorConfigValue(maxLineLengthProperty)
}
}

override fun beforeVisitChildNodes(
node: ASTNode,
autoCorrect: Boolean,
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
) {
if (node.elementType == FUN) {
node
.functionSignatureNodes()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.pinterest.ktlint.ruleset.standard

import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.api.EditorConfigProperties
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.core.initKtLintKLogger
import com.pinterest.ktlint.ruleset.standard.ImportOrderingRule.Companion.ASCII_PATTERN
import com.pinterest.ktlint.ruleset.standard.ImportOrderingRule.Companion.IDEA_PATTERN
Expand Down Expand Up @@ -47,128 +47,16 @@ public class ImportOrderingRule :
private lateinit var importsLayout: List<PatternEntry>
private lateinit var importSorter: ImportSorter

public companion object {
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 val ASCII_PATTERN = parseImportsLayout("*")

/**
* Default IntelliJ IDEA style: Alphabetical with capital letters before lower case letters (e.g. Z before a),
* except such groups as "java", "javax" and "kotlin" that are placed in the end. Within the groups the alphabetical order is preserved.
* Alias imports are placed in a separate group in the end of the list with alphabetical order inside.
* No blank lines between groups.
*
* https://github.com/JetBrains/kotlin/blob/ffdab473e28d0d872136b910eb2e0f4beea2e19c/idea/formatter/src/org/jetbrains/kotlin/idea/core/formatter/KotlinCodeStyleSettings.java#L87-L91
*/
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"
private const val ASCII_ERROR_MESSAGE = "Imports must be ordered in lexicographic order without any empty lines in-between"
private const val CUSTOM_ERROR_MESSAGE = "Imports must be ordered according to the pattern specified in .editorconfig"

private val errorMessages = mapOf(
IDEA_PATTERN to IDEA_ERROR_MESSAGE,
ASCII_PATTERN to ASCII_ERROR_MESSAGE
)

private val editorConfigPropertyParser: (String, String?) -> PropertyType.PropertyValue<List<PatternEntry>> =
{ _, value ->
when {
value.isNullOrBlank() -> PropertyType.PropertyValue.invalid(
value,
"Import layout must contain at least one entry of a wildcard symbol (*)"
)
value == "idea" -> {
logger.warn { "`idea` is deprecated! Please use `*,java.**,javax.**,kotlin.**,^` instead to ensure that the Kotlin IDE plugin recognizes the value" }
PropertyType.PropertyValue.valid(
value,
IDEA_PATTERN
)
}
value == "ascii" -> {
logger.warn { "`ascii` is deprecated! Please use `*` instead to ensure that the Kotlin IDE plugin recognizes the value" }
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"
)
}
}
}

public val ideaImportsLayoutProperty: UsesEditorConfigProperties.EditorConfigProperty<List<PatternEntry>> =
UsesEditorConfigProperties.EditorConfigProperty<List<PatternEntry>>(
type = PropertyType(
IDEA_IMPORTS_LAYOUT_PROPERTY_NAME,
PROPERTY_DESCRIPTION,
editorConfigPropertyParser
),
defaultValue = IDEA_PATTERN,
defaultAndroidValue = ASCII_PATTERN,
propertyWriter = { it.joinToString(separator = ",") }
)
}

private fun getUniqueImportsAndBlankLines(
children: Array<ASTNode>,
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
): Pair<Boolean, List<ASTNode>> {
var autoCorrectDuplicateImports = false
val imports = mutableListOf<ASTNode>()
val importTextSet = mutableSetOf<String>()

children.forEach { current ->
val isPsiWhiteSpace = current.psi is PsiWhiteSpace

if (current.elementType == ElementType.IMPORT_DIRECTIVE ||
isPsiWhiteSpace && current.textLength > 1 // also collect empty lines, that are represented as "\n\n"
) {
if (isPsiWhiteSpace || importTextSet.add(current.text)) {
imports += current
} else {
emit(
current.startOffset,
"Duplicate '${current.text}' found",
true
)
autoCorrectDuplicateImports = true
}
}
}

return autoCorrectDuplicateImports to imports
override fun beforeFirstNode(editorConfigProperties: EditorConfigProperties) {
importsLayout = editorConfigProperties.getEditorConfigValue(ideaImportsLayoutProperty)
importSorter = ImportSorter(importsLayout)
}

override fun beforeVisitChildNodes(
node: ASTNode,
autoCorrect: Boolean,
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
) {
if (node.isRoot()) {
importsLayout = node.getEditorConfigValue(ideaImportsLayoutProperty)
importSorter = ImportSorter(importsLayout)
return
}

if (node.elementType == ElementType.IMPORT_LIST) {
val children = node.getChildren(null)
if (children.isNotEmpty()) {
Expand Down Expand Up @@ -239,6 +127,36 @@ public class ImportOrderingRule :
}
}

private fun getUniqueImportsAndBlankLines(
children: Array<ASTNode>,
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
): Pair<Boolean, List<ASTNode>> {
var autoCorrectDuplicateImports = false
val imports = mutableListOf<ASTNode>()
val importTextSet = mutableSetOf<String>()

children.forEach { current ->
val isPsiWhiteSpace = current.psi is PsiWhiteSpace

if (current.elementType == ElementType.IMPORT_DIRECTIVE ||
isPsiWhiteSpace && current.textLength > 1 // also collect empty lines, that are represented as "\n\n"
) {
if (isPsiWhiteSpace || importTextSet.add(current.text)) {
imports += current
} else {
emit(
current.startOffset,
"Duplicate '${current.text}' found",
true
)
autoCorrectDuplicateImports = true
}
}
}

return autoCorrectDuplicateImports to imports
}

private fun importsAreEqual(actual: List<ASTNode>, expected: List<ASTNode>): Boolean {
if (actual.size != expected.size) return false

Expand All @@ -256,4 +174,85 @@ public class ImportOrderingRule :
private fun hasTooMuchWhitespace(nodes: Array<ASTNode>): Boolean {
return nodes.any { it is PsiWhiteSpace && (it as PsiWhiteSpace).text != "\n" }
}

public companion object {
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 val ASCII_PATTERN = parseImportsLayout("*")

/**
* Default IntelliJ IDEA style: Alphabetical with capital letters before lower case letters (e.g. Z before a),
* except such groups as "java", "javax" and "kotlin" that are placed in the end. Within the groups the alphabetical order is preserved.
* Alias imports are placed in a separate group in the end of the list with alphabetical order inside.
* No blank lines between groups.
*
* https://github.com/JetBrains/kotlin/blob/ffdab473e28d0d872136b910eb2e0f4beea2e19c/idea/formatter/src/org/jetbrains/kotlin/idea/core/formatter/KotlinCodeStyleSettings.java#L87-L91
*/
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"
private const val ASCII_ERROR_MESSAGE = "Imports must be ordered in lexicographic order without any empty lines in-between"
private const val CUSTOM_ERROR_MESSAGE = "Imports must be ordered according to the pattern specified in .editorconfig"

private val errorMessages = mapOf(
IDEA_PATTERN to IDEA_ERROR_MESSAGE,
ASCII_PATTERN to ASCII_ERROR_MESSAGE
)

private val editorConfigPropertyParser: (String, String?) -> PropertyType.PropertyValue<List<PatternEntry>> =
{ _, value ->
when {
value.isNullOrBlank() -> PropertyType.PropertyValue.invalid(
value,
"Import layout must contain at least one entry of a wildcard symbol (*)"
)
value == "idea" -> {
logger.warn { "`idea` is deprecated! Please use `*,java.**,javax.**,kotlin.**,^` instead to ensure that the Kotlin IDE plugin recognizes the value" }
PropertyType.PropertyValue.valid(
value,
IDEA_PATTERN
)
}
value == "ascii" -> {
logger.warn { "`ascii` is deprecated! Please use `*` instead to ensure that the Kotlin IDE plugin recognizes the value" }
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"
)
}
}
}

public val ideaImportsLayoutProperty: UsesEditorConfigProperties.EditorConfigProperty<List<PatternEntry>> =
UsesEditorConfigProperties.EditorConfigProperty<List<PatternEntry>>(
type = PropertyType(
IDEA_IMPORTS_LAYOUT_PROPERTY_NAME,
PROPERTY_DESCRIPTION,
editorConfigPropertyParser
),
defaultValue = IDEA_PATTERN,
defaultAndroidValue = ASCII_PATTERN,
propertyWriter = { it.joinToString(separator = ",") }
)
}
}

0 comments on commit beeaf55

Please sign in to comment.