Skip to content

Commit

Permalink
Retrieve value from ".editorconfig" properties directly from ASTNode (#…
Browse files Browse the repository at this point in the history
…1389)

From the perspective of the Rule developer, simplify the
retrieval of an ".editorconfig" property for the current
ASTNode. By using the ASTNode as receiver, it is no
longer needed to pass the "isAndroidCodeStyle" boolean
as well.

All logic regarding the determination of the actual
value of the property is now defined as part of the
definition of that property instead of being spread
around multiple places in the ktlint code. Especially
the handling of value "unset" (for properties
max_line_length and indent_size) and "off" (property
max_line_length) is now clearly centralized. This
closes #1387.

Property definitions indentStyleProperty,
indentSizeProperty, insertNewLineProperty and
maxLineLengthProperty are moved to the
DefaultEditorConfigProperties as those properties are
based on types provided by the ec4j library.

Class IndentConfig now needs to be initialized based
on the values of ".editorconfig" properties
indentStyleProperty and indentSizeProperty. Although,
this is less convenient, it is more explicit and
consistent with how rules should interact with the
".editorconfig" properties.

All rules provided by KtLint itself, now only use the
interface UsesEditorConfigProperties to retrieve values
for the ".editorconfig" property. As a result, the
EditConfig became obsolete. It is marked for deletion
in Ktlint 0.46.
  • Loading branch information
paul-dingemans authored Mar 12, 2022
1 parent 4079b94 commit 68990c8
Show file tree
Hide file tree
Showing 17 changed files with 522 additions and 118 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ This project adheres to [Semantic Versioning](https://semver.org/).

## Unreleased

### API Changes & RuleSet providers

If you are not an API user nor a RuleSet provider, then you can safely skip this section. Otherwise, please read below carefully and upgrade your usage of ktlint. In this and coming releases, we are changing and adapting important parts of our API in order to increase maintainability and flexibility for future changes. Please avoid skipping a releases as that will make it harder to migrate.

#### Retrieving ".editorconfig" property value

This section is applicable when providing rules that depend on one or more values of ".editorconfig" properties. Property values should no longer be retrieved via *EditConfig* or directly via `userData[EDITOR_CONFIG_USER_DATA_KEY]`. Property values should now only be retrieved using method `ASTNode.getEditorConfigValue(editorConfigProperty)` of interface `UsesEditorConfigProperties` which is provided in this release. Starting from next release after the current release, the *EditConfig* and/or `userData[EDITOR_CONFIG_USER_DATA_KEY]` may be removed without further notice which will break your API or rule. To prevent disruption of your end user, you should migrate a.s.a.p.

### Added
- Respect `.editorconfig` property `ij_kotlin_packages_to_use_import_on_demand` (`no-wildcard-imports`) ([#1272](https://github.com/pinterest/ktlint/pull/1272))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.pinterest.ktlint.core

import com.pinterest.ktlint.core.EditorConfig.IndentStyle.SPACE
import com.pinterest.ktlint.core.EditorConfig.IndentStyle.TAB
import com.pinterest.ktlint.core.api.DefaultEditorConfigProperties
import com.pinterest.ktlint.core.api.FeatureInAlphaState
import com.pinterest.ktlint.core.api.UsesEditorConfigProperties
import org.ec4j.core.model.PropertyType
Expand All @@ -12,29 +13,46 @@ import org.jetbrains.kotlin.com.intellij.lang.ASTNode
*
* This class is injected into the user data, so it is available to rules via [KtLint.EDITOR_CONFIG_USER_DATA_KEY]
*/
@Deprecated(
message = "Marked for removal in Ktlint 0.46. Implement interface UsesEditorConfigProperties on the rule and " +
"retrieve values via call 'node.getEditorConfigValue(property)'."
)
interface EditorConfig {
@Deprecated("Replace with IndentConfig.IndentStyle")
@Deprecated("Marked for removal in Ktlint 0.46. Replace with IndentConfig.IndentStyle")
enum class IndentStyle { SPACE, TAB }

@Deprecated("Prefer to use IndentConfig.indent only or IndentConfig.indentStyle otherwise")
@Deprecated("Marked for removal in Ktlint 0.46. Use IndentConfig.indent only or IndentConfig.indentStyle otherwise")
public val indentStyle: IndentStyle

@Deprecated("Prefer to use IndentConfig.indent.length")
@Deprecated("Marked for removal in Ktlint 0.46. Prefer to use IndentConfig.indent.length")
val indentSize: Int

@Deprecated("Prefer to use IndentConfig.indent.length")
@Deprecated("Marked for removal in Ktlint 0.46. Prefer to use IndentConfig.indent.length")
val tabWidth: Int

@Deprecated(
message = "Marked for removal in Ktlint 0.46. Implement interface UsesEditorConfigProperties on the rule and " +
"retrieve this value via call 'node.getEditorConfigValue(maxLineLengthProperty)'."
)
val maxLineLength: Int

@Deprecated(
message = "Not used anymore by rules, please use 'insert_final_newline' directly via get()"
message = "Marked for removal in Ktlint 0.46. Implement interface UsesEditorConfigProperties on the rule and " +
"retrieve this value via call 'node.getEditorConfigValue(insertNewLineProperty)'."
)
val insertFinalNewline: Boolean

@Deprecated(
message = "Marked for removal in Ktlint 0.46. Implement interface UsesEditorConfigProperties on the rule and " +
"retrieve values via call 'node.getEditorConfigValue(property)'."
)
operator fun get(key: String): String?

companion object {
@Deprecated(
message = "Marked for removal in Ktlint 0.46. Implement interface UsesEditorConfigProperties on the " +
"rule and retrieve values via call 'node.getEditorConfigValue(property)'."
)
fun fromMap(map: Map<String, String>): EditorConfig {
val indentStyle = when {
map["indent_style"]?.toLowerCase() == "tab" -> TAB
Expand Down Expand Up @@ -62,18 +80,29 @@ interface EditorConfig {
}
}

@Deprecated(
message = "Marked for removal in Ktlint 0.46. The interface UsesEditorConfigProperties needs to be " +
"implemented first on the rule.",
replaceWith = ReplaceWith("this.getEditorConfigValue(property)")
)
public fun ASTNode.loadEditorConfig(): EditorConfig = getUserData(KtLint.EDITOR_CONFIG_USER_DATA_KEY)!!

@Deprecated(
message = "Marked for removal in Ktlint 0.46. Implement interface UsesEditorConfigProperties on the " +
"rule. Then configure the IndentConfig inside the rule.",
replaceWith = ReplaceWith(
"IndentConfig(indentStyle = IndentStyleValue, tabWidth = tabWidth)",
"com.pinterest.ktlint.core.IndentConfig"
)
)
public fun EditorConfig.loadIndentConfig(): IndentConfig =
IndentConfig(
indentStyle = when (indentStyle) {
TAB -> IndentConfig.IndentStyle.TAB
SPACE -> IndentConfig.IndentStyle.SPACE
},
indentStyle = indentStyle,
tabWidth = tabWidth,
disabled = indentSize <= 0 || tabWidth <= 0
)

@Deprecated("Marked for removal in Ktlint 0.46.")
/**
* Use this value to define a non-nullable class variable of type EditorConfig. When loading the root node in
* rule visitor, the value should be replaced with the real value.
Expand All @@ -96,18 +125,20 @@ interface EditorConfig {
inner class EditorConfigNotInitializedException : RuntimeException("EditorConfig is not yet initialized")
}

@Deprecated(
message = "Marked for removal in Ktlint 0.46",
replaceWith = ReplaceWith("DefaultEditorConfigProperties.indentStyleProperty")
)
@OptIn(FeatureInAlphaState::class)
public val indentStyleProperty: UsesEditorConfigProperties.EditorConfigProperty<PropertyType.IndentStyleValue> =
UsesEditorConfigProperties.EditorConfigProperty(
type = PropertyType.indent_style,
defaultValue = PropertyType.IndentStyleValue.space
)
DefaultEditorConfigProperties.indentStyleProperty

@Deprecated(
message = "Marked for removal in Ktlint 0.46",
replaceWith = ReplaceWith("DefaultEditorConfigProperties.indentSizeProperty")
)
@OptIn(FeatureInAlphaState::class)
public val indentSizeProperty: UsesEditorConfigProperties.EditorConfigProperty<Int> =
UsesEditorConfigProperties.EditorConfigProperty(
type = PropertyType.indent_size,
defaultValue = IndentConfig.DEFAULT_INDENT_CONFIG.tabWidth
)
DefaultEditorConfigProperties.indentSizeProperty
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,69 @@ package com.pinterest.ktlint.core

import com.pinterest.ktlint.core.IndentConfig.IndentStyle.SPACE
import com.pinterest.ktlint.core.IndentConfig.IndentStyle.TAB
import org.ec4j.core.model.PropertyType

public data class IndentConfig(
val indentStyle: IndentStyle,

/**
* The number of spaces that is equivalent to one tab
*/
val tabWidth: Int,

val tabWidth: Int
) {
/**
* When disabled, rules may not enforce any indentation related changes regardless of other settings in this
* configuration.
* To use the [IndentConfig] in a rule, the following needs to be done:
* 1. Implement interface [UsesEditorConfigProperties] on the rule
* 2. Register the used or properties
* ```
* override val editorConfigProperties: List<UsesEditorConfigProperties.EditorConfigProperty<*>> =
* listOf(indentSizeProperty, indentStyleProperty)
* ```
* 3. Initialize the IndentConfig
* ```
* indentConfig = IndentConfig(
* indentStyle = node.getEditorConfigValue(indentStyleProperty),
* tabWidth = node.getEditorConfigValue(indentSizeProperty)
* )
* ```
*/
val disabled: Boolean = false
) {
public constructor(
indentStyle: PropertyType.IndentStyleValue,

/**
* The number of spaces that is equivalent to one tab
*/
tabWidth: Int
) : this(
indentStyle = when (indentStyle) {
PropertyType.IndentStyleValue.tab -> TAB
PropertyType.IndentStyleValue.space -> SPACE
},
tabWidth = tabWidth
)

@Deprecated(message = "Marked for removal in Ktlint 0.46.")
public constructor(
indentStyle: EditorConfig.IndentStyle,

/**
* The number of spaces that is equivalent to one tab
*/
tabWidth: Int,

/**
* Property is ignored (starting from ktlint 0.45). Specify a tabWidth <= 0 to disable the indent.
*/
@Suppress("UNUSED_PARAMETER")
disabled: Boolean = false
) : this(
indentStyle = when (indentStyle) {
EditorConfig.IndentStyle.TAB -> TAB
EditorConfig.IndentStyle.SPACE -> SPACE
},
tabWidth = tabWidth
)

public enum class IndentStyle { SPACE, TAB }

private val indentChar =
Expand All @@ -31,6 +79,13 @@ public data class IndentConfig(
SPACE -> '\t'
}

/**
* When disabled, rules may not enforce any indentation related changes regardless of other settings in this
* configuration.
*/
public val disabled: Boolean
get() = tabWidth <= 0

/**
* Normalized indent of 1 level deep. Representation is either in spaces or a single tab depending on the
* configuration.
Expand Down Expand Up @@ -110,8 +165,7 @@ public data class IndentConfig(

public val DEFAULT_INDENT_CONFIG: IndentConfig = IndentConfig(
indentStyle = SPACE,
tabWidth = 4,
disabled = false
tabWidth = 4
)
}
}
18 changes: 6 additions & 12 deletions ktlint-core/src/main/kotlin/com/pinterest/ktlint/core/KtLint.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public object KtLint {
message = "Marked for removal in Ktlint 0.46.",
replaceWith = ReplaceWith("EDITOR_CONFIG_PROPERTIES_USER_DATA_KEY")
)
// TODO: when removing, then also remove method "EditorConfigProperties.toUserData"
public val EDITOR_CONFIG_USER_DATA_KEY: Key<EditorConfig> = Key<EditorConfig>("EDITOR_CONFIG")

public val ANDROID_USER_DATA_KEY: Key<Boolean> = Key<Boolean>("ANDROID")
Expand Down Expand Up @@ -239,9 +240,7 @@ public object KtLint {
params.debug
)

// Passed-in userData overrides .editorconfig
val mergedUserData = editorConfigProperties
.convertToRawValues() + params.userData
val mergedUserData = editorConfigProperties.toUserData() + params.userData
.run {
if (!params.isStdIn) {
plus(FILE_PATH_PROPERTY to params.normalizedFilePath.toString())
Expand All @@ -261,6 +260,9 @@ public object KtLint {
)
}

@Deprecated("Remove in Ktlint 0.46 when deleting EDITOR_CONFIG_USER_DATA_KEY.")
private fun EditorConfigProperties.toUserData() = convertToRawValues()

@Deprecated(
message = "Should not be a part of public api. Will be removed in future release.",
level = DeprecationLevel.WARNING
Expand All @@ -280,16 +282,8 @@ public object KtLint {
userData: Map<String, String>
) {
val android = userData.isAndroidCodeStyle
val editorConfigMap =
if (android &&
userData["max_line_length"].let { it?.toLowerCase() != "off" && it?.toIntOrNull() == null }
) {
userData + mapOf("max_line_length" to "100")
} else {
userData
}
node.putUserData(FILE_PATH_USER_DATA_KEY, userData[FILE_PATH_PROPERTY])
node.putUserData(EDITOR_CONFIG_USER_DATA_KEY, EditorConfig.fromMap(editorConfigMap - "android" - "file_path"))
node.putUserData(EDITOR_CONFIG_USER_DATA_KEY, EditorConfig.fromMap(userData - "android" - "file_path"))
node.putUserData(EDITOR_CONFIG_PROPERTIES_USER_DATA_KEY, editorConfigProperties)
node.putUserData(ANDROID_USER_DATA_KEY, android)
node.putUserData(
Expand Down
Loading

0 comments on commit 68990c8

Please sign in to comment.