Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to enable specific experimental rules #1023

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).

## Unreleased
### Added
- Support for globally enabling experimental rules via `--experimental_rules` command line flag.
- Support for globally enabling experimental rules via custom `experimental_rules` property in `.editorconfig`

### Fixed
- Incorrect indentation with multiple interfaces ([#1003](https://github.com/pinterest/ktlint/issues/1003))
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ max_line_length=off
# Note that rules in any ruleset other than the standard ruleset will need to be prefixed
# by the ruleset identifier.
disabled_rules=no-wildcard-imports,experimental:annotation,my-custom-ruleset:my-custom-rule
experimental_rules=multiline-if-else,annotation

# Defines the imports layout. The layout can be composed by the following symbols:
# "*" - wildcard. There must be at least one entry of a single wildcard to match all other imports. Matches anything after a specified symbol/import as well.
Expand Down Expand Up @@ -533,6 +534,13 @@ See the [EditorConfig section](https://github.com/pinterest/ktlint#editorconfig)

You may also pass a list of disabled rules via the `--disabled_rules` command line flag. It has the same syntax as the EditorConfig property.

### How do I globally enable an experimental rule?
Experimental rules are not enabled by default.

See the [EditorConfig section](https://github.com/pinterest/ktlint#editorconfig) for details on how to use the `experimental_rules` property.

You may also pass a list of enabled experimental rules via the `--experimental_rules` command line flag. It has the same syntax as the EditorConfig property.

## Development

> Make sure to read [CONTRIBUTING.md](CONTRIBUTING.md).
Expand Down
34 changes: 30 additions & 4 deletions ktlint-core/src/main/kotlin/com/pinterest/ktlint/core/KtLint.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public object KtLint {
public val EDITOR_CONFIG_PROPERTIES_USER_DATA_KEY: Key<EditorConfigProperties> =
Key<EditorConfigProperties>("EDITOR_CONFIG_PROPERTIES")
public val DISABLED_RULES: Key<Set<String>> = Key<Set<String>>("DISABLED_RULES")
private val EXPERIMENTAL_RULES: Key<Set<String>> = Key<Set<String>>("EXPERIMENTAL_RULES")
private const val UTF8_BOM = "\uFEFF"
public const val STDIN_FILE: String = "<stdin>"

Expand Down Expand Up @@ -202,13 +203,17 @@ public object KtLint {
DISABLED_RULES,
userData["disabled_rules"]?.split(",")?.map { it.trim() }?.toSet() ?: emptySet()
)
node.putUserData(
EXPERIMENTAL_RULES,
userData["experimental_rules"]?.split(",")?.map { it.trim() }?.toSet() ?: emptySet()
)
}

private fun visitor(
rootNode: ASTNode,
ruleSets: Iterable<RuleSet>,
concurrent: Boolean = true,
filter: (rootNode: ASTNode, fqRuleId: String) -> Boolean = this::filterDisabledRules
filter: (rootNode: ASTNode, ruleSet: RuleSet, fqRuleId: String) -> Boolean = this::isEnabledRule

): ((node: ASTNode, rule: Rule, fqRuleId: String) -> Unit) -> Unit {
val fqrsRestrictedToRoot = mutableListOf<Pair<String, Rule>>()
Expand All @@ -219,7 +224,7 @@ public object KtLint {
val prefix = if (ruleSet.id === "standard") "" else "${ruleSet.id}:"
for (rule in ruleSet) {
val fqRuleId = "$prefix${rule.id}"
if (!filter(rootNode, fqRuleId)) {
if (!filter(rootNode, ruleSet, fqRuleId)) {
continue
}
val fqr = fqRuleId to rule
Expand Down Expand Up @@ -269,10 +274,31 @@ public object KtLint {
}
}

private fun filterDisabledRules(rootNode: ASTNode, fqRuleId: String): Boolean {
return rootNode.getUserData(DISABLED_RULES)?.contains(fqRuleId) == false
private fun isEnabledRule(rootNode: ASTNode, ruleSet: RuleSet, fqRuleId: String): Boolean {
return if (ruleSet.id == "experimental" && hasEnabledExperimentalRules(rootNode)) {
isEnabledExperimentalRule(rootNode, fqRuleId)
} else {
isNotDisabledRule(rootNode, fqRuleId)
}
}

private fun hasEnabledExperimentalRules(rootNode: ASTNode) =
!rootNode.getUserData(EXPERIMENTAL_RULES).isNullOrEmpty()

private fun isNotDisabledRule(
rootNode: ASTNode,
fqRuleId: String
) = rootNode.getUserData(DISABLED_RULES)?.contains(fqRuleId) == false

private fun isEnabledExperimentalRule(
rootNode: ASTNode,
fqRuleId: String
) = rootNode.getUserData(EXPERIMENTAL_RULES)
.orEmpty()
.let {
it.contains("all") || it.contains(fqRuleId.removePrefix("experimental:"))
}

@Deprecated(
message = "Should not be a part of public api. Will be removed in future release.",
level = DeprecationLevel.WARNING
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ internal class EditorConfigLoaderTest {
[*.{kt,kts}]
insert_final_newline = true
disabled_rules = import-ordering
experimental_rules = annotation
""".trimIndent()
tempFileSystem.writeEditorConfigFile(projectDir, editorconfigFile)

Expand All @@ -182,6 +183,7 @@ internal class EditorConfigLoaderTest {
mapOf(
"insert_final_newline" to "true",
"disabled_rules" to "import-ordering",
"experimental_rules" to "annotation"
)
)
}
Expand Down Expand Up @@ -216,6 +218,7 @@ internal class EditorConfigLoaderTest {
"""
[*.{kt,kts}]
disabled_rules=import-ordering, no-wildcard-imports
experimental_rules=annotation, multiline-if-else
""".trimIndent()
tempFileSystem.writeEditorConfigFile(projectDir, editorconfigFile)
val lintFile = tempFileSystem.normalizedPath(projectDir).resolve("test.kts")
Expand All @@ -227,6 +230,7 @@ internal class EditorConfigLoaderTest {
assertThat(parsedEditorConfig).isEqualTo(
mapOf(
"disabled_rules" to "import-ordering, no-wildcard-imports",
"experimental_rules" to "annotation, multiline-if-else"
)
)
}
Expand Down Expand Up @@ -256,6 +260,7 @@ internal class EditorConfigLoaderTest {
[*.{kt,kts}]
insert_final_newline = true
disabled_rules = import-ordering
experimental_rules = annotation
""".trimIndent()
tempFileSystem.writeEditorConfigFile(".", editorconfigFile)

Expand All @@ -271,7 +276,8 @@ internal class EditorConfigLoaderTest {
assertThat(parsedEditorConfig).isEqualTo(
mapOf(
"insert_final_newline" to "true",
"disabled_rules" to "import-ordering"
"disabled_rules" to "import-ordering",
"experimental_rules" to "annotation"
)
)
}
Expand Down Expand Up @@ -380,9 +386,11 @@ internal class EditorConfigLoaderTest {
[*.{kt,kts}]
insert_final_newline = true
disabled_rules = import-ordering
experimental_rules = annotation

[api/*.{kt,kts}]
disabled_rules = class-must-be-internal
experimental_rules = multiline-if-else
""".trimIndent()
tempFileSystem.writeEditorConfigFile(projectDir, editorconfigFile)

Expand All @@ -397,6 +405,7 @@ internal class EditorConfigLoaderTest {
mapOf(
"insert_final_newline" to "true",
"disabled_rules" to "class-must-be-internal",
"experimental_rules" to "multiline-if-else"
)
)
}
Expand Down
17 changes: 15 additions & 2 deletions ktlint/src/main/kotlin/com/pinterest/ktlint/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,15 @@ class KtlintCommandLine {
)
var experimental: Boolean = false

@Option(
names = ["--experimental_rules"],
description = [
"Comma-separated list of experimental rules to globally enable." +
" To enable all experimental rules use --experimental"
]
)
var experimentalRules: String = ""

@Option(
names = ["--baseline"],
description = ["Defines a baseline file to check against"]
Expand All @@ -237,7 +246,10 @@ class KtlintCommandLine {
val start = System.currentTimeMillis()

val baselineResults = loadBaseline(baseline)
val ruleSetProviders = rulesets.loadRulesets(experimental, debug, disabledRules)
if (experimental) {
experimentalRules = "all"
}
val ruleSetProviders = rulesets.loadRulesets(debug, disabledRules, experimentalRules)
var reporter = loadReporter()
if (baselineResults.baselineGenerationNeeded) {
val baselineReporter = ReporterTemplate("baseline", null, emptyMap(), baseline)
Expand All @@ -246,7 +258,8 @@ class KtlintCommandLine {
}
val userData = listOfNotNull(
"android" to android.toString(),
if (disabledRules.isNotBlank()) "disabled_rules" to disabledRules else null
if (disabledRules.isNotBlank()) "disabled_rules" to disabledRules else null,
if (experimentalRules.isNotBlank()) "experimental_rules" to experimentalRules else null
).toMap()

reporter.beforeAll()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ class GenerateEditorConfigSubCommand : Runnable {
text = "",
ruleSets = ktlintCommand.rulesets
.loadRulesets(
ktlintCommand.experimental,
ktlintCommand.debug,
ktlintCommand.disabledRules
ktlintCommand.disabledRules,
ktlintCommand.experimentalRules
)
.map { it.value.get() },
userData = mapOf(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import java.util.ServiceLoader
* @return map of ruleset ids to ruleset providers
*/
internal fun JarFiles.loadRulesets(
loadExperimental: Boolean,
debug: Boolean,
disabledRules: String
disabledRules: String,
enabledExperimentalRules: String
) = ServiceLoader
.load(
RuleSetProvider::class.java,
Expand All @@ -23,7 +23,7 @@ internal fun JarFiles.loadRulesets(
// standard should go first
if (key == "standard") "\u0000$key" else key
}
.filterKeys { loadExperimental || it != "experimental" }
.filterKeys { it != "experimental" || !enabledExperimentalRules.isNullOrEmpty() }
.filterKeys { !(disabledRules.isStandardRuleSetDisabled() && it == "\u0000standard") }
.toSortedMap()
.also {
Expand Down