Skip to content

Commit 71c553b

Browse files
Fix backward incompatibility issues released via KtLint 0.45.0 and 0.45.1 (#1442)
* Fix backward incompatibility issues released via KtLint 0.45.0 and 0.45.1. * Deprecate data class 'Params' as it only provides the userData parameter which prohibits API Consumers to split the '.editorconfig' properties from the other user data. * Deprecate function 'toExperimentalParams' to force API Consumers to start using the 'ExperimentalParams'. * In ExperimentalParams clarify that 'userData' should not contain any '.editorconfig' properties. Now also a runtime error is thrown whenever the lint/format methods are called and 'userData' contains '.editorconfig' properties. * Remove confusing TODO's about moving methods 'lint' and 'format' to the 'ktlint-test' module. * Update deprecation messages to clarify intention. * The 'lint' and 'format' methods in module 'ktlint-core' no longer call the 'VisitorProvider' with parameter 'isUnitTestContext' enabled. API Consumers call these methods with production code for which this parameter should be disabled. * The RuleExtension calls the 'lint' and 'format' methods but do provide a VisitorProvider for which the parameter 'isUnitTestContext' is enabled. * Deprecate the 'format' which accepted the 'ExperimentalParams' and 'Iterable<RuleSet>' while the latter is already included in the first. * Add new 'format' method which accepts 'ExperimentalParams' and 'VisitorProvider' only. * Move 'EditorConfigOverride' from 'ktlint-test' to 'ktlint-core' Closes #1434
1 parent f5cfce3 commit 71c553b

19 files changed

Lines changed: 840 additions & 79 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
77
### Added
88

99
### Fixed
10+
- Resolve compatibility issues introduced in 0.45.0 and 0.45.1 ([#1434](https://github.com/pinterest/ktlint/issues/1434))
1011

1112
### Changed
1213
* Set Kotlin development version to `1.6.20` and Kotlin version to `1.6.20`.

ktlint-core/src/main/kotlin/com/pinterest/ktlint/core/KtLint.kt

Lines changed: 66 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package com.pinterest.ktlint.core
22

3+
import com.pinterest.ktlint.core.api.DefaultEditorConfigProperties
4+
import com.pinterest.ktlint.core.api.EditorConfigOverride
5+
import com.pinterest.ktlint.core.api.EditorConfigOverride.Companion.emptyEditorConfigOverride
36
import com.pinterest.ktlint.core.api.EditorConfigProperties
47
import com.pinterest.ktlint.core.api.FeatureInAlphaState
58
import com.pinterest.ktlint.core.api.UsesEditorConfigProperties
69
import com.pinterest.ktlint.core.internal.EditorConfigGenerator
710
import com.pinterest.ktlint.core.internal.EditorConfigLoader
811
import com.pinterest.ktlint.core.internal.EditorConfigLoader.Companion.convertToRawValues
9-
import com.pinterest.ktlint.core.internal.EditorConfigOverridesMap
1012
import com.pinterest.ktlint.core.internal.KotlinPsiFileFactoryProvider
1113
import com.pinterest.ktlint.core.internal.LineAndColumn
1214
import com.pinterest.ktlint.core.internal.SuppressionLocator
@@ -59,6 +61,10 @@ public object KtLint {
5961
* @param editorConfigPath optional path of the .editorconfig file (otherwise will use working directory)
6062
* @param debug True if invoked with the --debug flag
6163
*/
64+
@Deprecated(
65+
message = "Marked for removal in Ktlint 0.46.",
66+
replaceWith = ReplaceWith("ExperimentalParams")
67+
)
6268
public data class Params(
6369
val fileName: String? = null,
6470
val text: String,
@@ -74,7 +80,7 @@ public object KtLint {
7480
* @param fileName path of file to lint/format
7581
* @param text Contents of file to lint/format
7682
* @param ruleSets a collection of "RuleSet"s used to validate source
77-
* @param userData Map of user options
83+
* @param userData Map of user options. Should not be used for overrides of properties set in '.editorconfig'.
7884
* @param cb callback invoked for each lint error
7985
* @param script true if this is a Kotlin script file
8086
* @param editorConfigPath optional path of the .editorconfig file (otherwise will use working directory)
@@ -100,9 +106,37 @@ public object KtLint {
100106
val script: Boolean = false,
101107
val editorConfigPath: String? = null,
102108
val debug: Boolean = false,
103-
val editorConfigOverride: EditorConfigOverridesMap = emptyMap(),
109+
val editorConfigOverride: EditorConfigOverride = emptyEditorConfigOverride,
104110
val isInvokedFromCli: Boolean = false
105111
) {
112+
init {
113+
// Extract all default and custom ".editorconfig" properties which are defined using the
114+
// [UsesEditorConfigProperties] interface of the rule
115+
val editorConfigProperties =
116+
ruleSets
117+
.asSequence()
118+
.flatten()
119+
.filterIsInstance<UsesEditorConfigProperties>()
120+
.map { it.editorConfigProperties }
121+
.flatten()
122+
.plus(DefaultEditorConfigProperties.defaultEditorConfigProperties)
123+
.map { it.type.name }
124+
.distinct()
125+
.toSet()
126+
127+
userData
128+
.keys
129+
.intersect(editorConfigProperties)
130+
.let {
131+
check(it.isEmpty()) {
132+
"UserData should not contain '.editorconfig' properties ${it.sorted()}. Such properties " +
133+
"should be passed via the 'ExperimentalParams.editorConfigOverride' field. Note that this is " +
134+
"only required for properties that (potentially) contain a value that differs from the " +
135+
"actual value in the '.editorconfig' file."
136+
}
137+
}
138+
}
139+
106140
internal val normalizedFilePath: Path? get() = if (fileName == STDIN_FILE || fileName == null) {
107141
null
108142
} else {
@@ -118,6 +152,10 @@ public object KtLint {
118152
.toSet()
119153
}
120154

155+
@Deprecated(
156+
message = "Marked for removal in Ktlint 0.46.",
157+
replaceWith = ReplaceWith("ExperimentalParams")
158+
)
121159
@OptIn(FeatureInAlphaState::class)
122160
private fun toExperimentalParams(params: Params): ExperimentalParams =
123161
ExperimentalParams(
@@ -137,10 +175,8 @@ public object KtLint {
137175
* @throws ParseException if text is not a valid Kotlin code
138176
* @throws RuleExecutionException in case of internal failure caused by a bug in rule implementation
139177
*/
140-
// TODO: Shouldn't this method be moved to module ktlint-test as it is called from unit tests only? It will be a
141-
// breaking change for custom rule set implementations.
142178
@Deprecated(
143-
message = "Marked for removal in Ktlint 0.46. Convert userData to EditorConfigOverride.",
179+
message = "Marked for removal in Ktlint 0.46. Move '.editorconfig' properties from 'Params.userData' to 'ExperimentalParam.editorConfigOverride'.",
144180
replaceWith = ReplaceWith("lint(toExperimentalParams(params))")
145181
)
146182
@OptIn(FeatureInAlphaState::class)
@@ -153,8 +189,7 @@ public object KtLint {
153189
experimentalParams,
154190
VisitorProvider(
155191
ruleSets = experimentalParams.ruleSets,
156-
debug = experimentalParams.debug,
157-
isUnitTestContext = true
192+
debug = experimentalParams.debug
158193
)
159194
)
160195
}
@@ -165,8 +200,6 @@ public object KtLint {
165200
* @throws ParseException if text is not a valid Kotlin code
166201
* @throws RuleExecutionException in case of internal failure caused by a bug in rule implementation
167202
*/
168-
// TODO: Shouldn't this method be moved to module ktlint-test as it is called from unit tests only? It will be a
169-
// breaking change for custom rule set implementations.
170203
@FeatureInAlphaState
171204
public fun lint(
172205
params: ExperimentalParams,
@@ -308,8 +341,10 @@ public object KtLint {
308341
* @throws ParseException if text is not a valid Kotlin code
309342
* @throws RuleExecutionException in case of internal failure caused by a bug in rule implementation
310343
*/
311-
// TODO: Shouldn't this method be moved to module ktlint-test as it is called from unit tests only? It will be a
312-
// breaking change for custom rule set implementations.
344+
@Deprecated(
345+
message = "Marked for removal in Ktlint 0.46. Move '.editorconfig' properties from 'Params.userData' to 'ExperimentalParam.editorConfigOverride'.",
346+
replaceWith = ReplaceWith("lint(toExperimentalParams(params))")
347+
)
313348
@OptIn(FeatureInAlphaState::class)
314349
public fun format(params: Params): String =
315350
format(toExperimentalParams(params))
@@ -321,8 +356,7 @@ public object KtLint {
321356
experimentalParams.ruleSets,
322357
VisitorProvider(
323358
ruleSets = experimentalParams.ruleSets,
324-
debug = experimentalParams.debug,
325-
isUnitTestContext = true
359+
debug = experimentalParams.debug
326360
)
327361
)
328362
}
@@ -333,11 +367,27 @@ public object KtLint {
333367
* @throws ParseException if text is not a valid Kotlin code
334368
* @throws RuleExecutionException in case of internal failure caused by a bug in rule implementation
335369
*/
370+
@Deprecated(
371+
message = "Marked for removal in Ktlint 0.46. Overrides of '.editorconfig' properties no longer should be " +
372+
"passed via the 'Params.userData' but via the 'ExperimentalParam.editorConfigOverride' parameter. The " +
373+
"ruleSets have to be provided via the 'ExperimentalParams.ruleSets' parameter.",
374+
replaceWith = ReplaceWith("format(params, visitorProvider)")
375+
)
336376
@FeatureInAlphaState
337377
public fun format(
338378
params: ExperimentalParams,
339379
ruleSets: Iterable<RuleSet>,
340380
visitorProvider: VisitorProvider
381+
): String =
382+
format(
383+
params.copy(ruleSets = ruleSets),
384+
visitorProvider
385+
)
386+
387+
@FeatureInAlphaState
388+
public fun format(
389+
params: ExperimentalParams,
390+
visitorProvider: VisitorProvider
341391
): String {
342392
val hasUTF8BOM = params.text.startsWith(UTF8_BOM)
343393
val psiFileFactory = kotlinPsiFileFactoryProvider.getKotlinPsiFileFactory(params.isInvokedFromCli)
@@ -347,7 +397,7 @@ public object KtLint {
347397
var mutated = false
348398
visitorProvider
349399
.visitor(
350-
ruleSets,
400+
params.ruleSets,
351401
preparedCode.rootNode,
352402
concurrent = false
353403
).invoke { node, rule, fqRuleId ->
@@ -378,7 +428,7 @@ public object KtLint {
378428
if (tripped) {
379429
val errors = mutableListOf<Pair<LintError, Boolean>>()
380430
visitorProvider
381-
.visitor(ruleSets, preparedCode.rootNode)
431+
.visitor(params.ruleSets, preparedCode.rootNode)
382432
.invoke { node, rule, fqRuleId ->
383433
// fixme: enforcing suppression based on node.startOffset is wrong
384434
// (not just because not all nodes are leaves but because rules are free to emit (and fix!) errors at any position)

ktlint-test/src/main/kotlin/com/pinterest/ktlint/test/EditorConfigOverride.kt renamed to ktlint-core/src/main/kotlin/com/pinterest/ktlint/core/api/EditorConfigOverride.kt

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
1-
package com.pinterest.ktlint.test
1+
package com.pinterest.ktlint.core.api
22

3-
import com.pinterest.ktlint.core.api.FeatureInAlphaState
43
import com.pinterest.ktlint.core.api.UsesEditorConfigProperties.EditorConfigProperty
54
import org.ec4j.core.model.PropertyType
65

76
/**
8-
* Properties set in [EditorConfigOverride] override values which are loaded from the ".EditorConfig" file. This
9-
* [EditorConfigOverride] is only to be used in unit tests. From the perspective of the unit test as well as from the
10-
* perspective of the rule, it is transparent whether an editor config property was loaded from an ".editorconfig" file
11-
* or from an override.
7+
* The [EditorConfigOverride] allows to add or replace properties which are loaded from the ".editorconfig" file. It
8+
* serves two purposes.
9+
*
10+
* Firstly, the [EditorConfigOverride] can be used by API consumers to run a rule with values which are not actually
11+
* save to the ".editorconfig" file. When doing so, this should be clearly communicated to their consumers who will
12+
* expect the settings in that file to be respected.
13+
*
14+
* Secondly, the [EditorConfigOverride] is used in unit tests, to test a rule with distinct values of a property without
15+
* having to access an ".editorconfig" file from physical storage. This also improves readability of the tests.
1216
*/
1317
@FeatureInAlphaState
1418
public class EditorConfigOverride {
@@ -22,8 +26,7 @@ public class EditorConfigOverride {
2226

2327
public companion object {
2428
/**
25-
* Creates EditorConfig properties with value to be used in unit test so that the unit test does not need to
26-
* write an .editorconfig file.
29+
* Creates the [EditorConfigOverride] based on one or more property-value mappings.
2730
*/
2831
public fun from(
2932
vararg properties: Pair<EditorConfigProperty<*>, *>
@@ -40,8 +43,8 @@ public class EditorConfigOverride {
4043
}
4144

4245
/**
43-
* Get the empty EditorConfig properties. As it does not contain any properties, all properties fall back on
44-
* their respective default value.
46+
* Get the empty [EditorConfigOverride]. As it does not contain any properties, all properties fall back on
47+
* their respective default values.
4548
*/
4649
public val emptyEditorConfigOverride: EditorConfigOverride = EditorConfigOverride()
4750
}

ktlint-core/src/main/kotlin/com/pinterest/ktlint/core/api/UsesEditorConfigProperties.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,13 @@ public object DefaultEditorConfigProperties {
200200
}
201201
}
202202
)
203+
204+
public val defaultEditorConfigProperties: List<UsesEditorConfigProperties.EditorConfigProperty<out Any>> = listOf(
205+
indentStyleProperty,
206+
indentSizeProperty,
207+
insertNewLineProperty,
208+
maxLineLengthProperty
209+
)
203210
}
204211

205212
/**

ktlint-core/src/main/kotlin/com/pinterest/ktlint/core/internal/EditorConfigLoader.kt

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.pinterest.ktlint.core.internal
22

33
import com.pinterest.ktlint.core.Rule
4+
import com.pinterest.ktlint.core.api.EditorConfigOverride
5+
import com.pinterest.ktlint.core.api.EditorConfigOverride.Companion.emptyEditorConfigOverride
46
import com.pinterest.ktlint.core.api.EditorConfigProperties
57
import com.pinterest.ktlint.core.api.FeatureInAlphaState
68
import com.pinterest.ktlint.core.api.UsesEditorConfigProperties
@@ -23,6 +25,10 @@ private val logger = KotlinLogging.logger {}.initKtLintKLogger()
2325
* Map contains [UsesEditorConfigProperties.EditorConfigProperty] and related
2426
* [PropertyType.PropertyValue] entries to add/replace loaded from `.editorconfig` files values.
2527
*/
28+
@Deprecated(
29+
message = "Marked for removal in KtLint 0.46.",
30+
replaceWith = ReplaceWith("EditorConfigOverride")
31+
)
2632
@FeatureInAlphaState
2733
public typealias EditorConfigOverridesMap =
2834
Map<UsesEditorConfigProperties.EditorConfigProperty<*>, PropertyType.PropertyValue<*>>
@@ -48,7 +54,7 @@ public class EditorConfigLoader(
4854
* @param alternativeEditorConfig alternative to current [filePath] location where `.editorconfig` files should be
4955
* looked up
5056
* @param rules set of [Rule]s linting the file
51-
* @param loadedValuesOverride map of values to add/replace values that were loaded from `.editorconfig` files
57+
* @param editorConfigOverride map of values to add/replace values that were loaded from `.editorconfig` files
5258
* @param debug pass `true` to enable some additional debug output
5359
*
5460
* @return all possible loaded properties applicable to given file.
@@ -60,13 +66,14 @@ public class EditorConfigLoader(
6066
isStdIn: Boolean = false,
6167
alternativeEditorConfig: Path? = null,
6268
rules: Set<Rule>,
63-
loadedValuesOverride: EditorConfigOverridesMap = emptyMap(),
69+
editorConfigOverride: EditorConfigOverride = emptyEditorConfigOverride,
6470
debug: Boolean = false
6571
): EditorConfigProperties {
6672
if (!isStdIn &&
6773
(filePath == null || SUPPORTED_FILES.none { filePath.toString().endsWith(it) })
6874
) {
69-
return loadedValuesOverride
75+
return editorConfigOverride
76+
.properties
7077
.map { (property, value) ->
7178
property.type.name to Property.builder()
7279
.name(property.type.name)
@@ -99,13 +106,15 @@ public class EditorConfigLoader(
99106
)
100107
.properties
101108
.also { loaded ->
102-
loadedValuesOverride.forEach {
103-
loaded[it.key.type.name] = Property.builder()
104-
.name(it.key.type.name)
105-
.type(it.key.type)
106-
.value(it.value)
107-
.build()
108-
}
109+
editorConfigOverride
110+
.properties
111+
.forEach {
112+
loaded[it.key.type.name] = Property.builder()
113+
.name(it.key.type.name)
114+
.type(it.key.type)
115+
.value(it.value)
116+
.build()
117+
}
109118
}
110119
.also {
111120
logger.trace {

0 commit comments

Comments
 (0)