Skip to content

Commit

Permalink
support ktlint 0.45-0.48
Browse files Browse the repository at this point in the history
This resolves #589 and #607
  • Loading branch information
wakingrufus committed Jan 12, 2023
1 parent 89b7fb6 commit 777b6cd
Show file tree
Hide file tree
Showing 5 changed files with 344 additions and 44 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
package org.jlleitschuh.gradle.ktlint.worker

import com.pinterest.ktlint.core.LintError
import com.pinterest.ktlint.core.api.FeatureInAlphaState
import java.io.File
import kotlin.reflect.full.declaredMemberFunctions
import kotlin.reflect.full.findParameterByName
import kotlin.reflect.full.instanceParameter
import kotlin.reflect.full.memberFunctions
import kotlin.reflect.full.memberProperties
import kotlin.reflect.full.primaryConstructor

/**
* An abstraction for invoking ktlint across all breaking changes between versions
*/
internal sealed interface KtLintInvocation {
fun invokeLint(file: File, cb: (LintError, Boolean) -> Unit)
fun invokeFormat(file: File, cb: (LintError, Boolean) -> Unit): String
}

internal class LegacyParamsInvocation : KtLintInvocation {
private var editorConfigPath: String? = null
private var debug: Boolean = false
private lateinit var ruleSets: Set<com.pinterest.ktlint.core.RuleSet>
private lateinit var userData: Map<String, String>
fun initialize(
editorConfigPath: String?,
ruleSets: Set<com.pinterest.ktlint.core.RuleSet>,
userData: Map<String, String>,
debug: Boolean
) {
this.editorConfigPath = editorConfigPath
this.ruleSets = ruleSets
this.userData = userData
this.debug = debug
}
private fun buildParams(file: File, cb: (LintError, Boolean) -> Unit): com.pinterest.ktlint.core.KtLint.Params {
val script = !file.name.endsWith(".kt", ignoreCase = true)
return com.pinterest.ktlint.core.KtLint.Params(
fileName = file.absolutePath,
text = file.readText(),
ruleSets = ruleSets,
userData = userData,
debug = debug,
editorConfigPath = editorConfigPath,
script = script,
cb = cb
)
}

override fun invokeLint(file: File, cb: (LintError, Boolean) -> Unit) {
com.pinterest.ktlint.core.KtLint.lint(buildParams(file, cb))
}

override fun invokeFormat(file: File, cb: (LintError, Boolean) -> Unit): String {
return com.pinterest.ktlint.core.KtLint.format(buildParams(file, cb))
}

}

@OptIn(FeatureInAlphaState::class)
internal class ExperimentalParamsInvocation : KtLintInvocation {
private var editorConfigPath: String? = null
private var debug: Boolean = false
private lateinit var ruleSets: Set<com.pinterest.ktlint.core.RuleSet>
private lateinit var userData: Map<String, String>
fun initialize(
editorConfigPath: String?,
ruleSets: Set<com.pinterest.ktlint.core.RuleSet>,
userData: Map<String, String>,
debug: Boolean
) {
this.editorConfigPath = editorConfigPath
this.ruleSets = ruleSets
this.userData = userData
this.debug = debug
}

private fun buildParams(file: File, cb: (LintError, Boolean) -> Unit): com.pinterest.ktlint.core.KtLint.ExperimentalParams {
val script = !file.name.endsWith(".kt", ignoreCase = true)
val ctor = Class.forName("com.pinterest.ktlint.core.KtLint\$ExperimentalParams").kotlin.primaryConstructor
val editorConfigOverride = userDataToEditorConfigOverride(userData)
return ctor!!.callBy(
mapOf(
ctor.findParameterByName("fileName")!! to file.absolutePath,
ctor.findParameterByName("text")!! to file.readText(),
ctor.findParameterByName("ruleSets")!! to ruleSets,
ctor.findParameterByName("cb")!! to cb,
ctor.findParameterByName("script")!! to script,
ctor.findParameterByName("editorConfigPath")!! to editorConfigPath,
ctor.findParameterByName("debug")!! to debug,
ctor.findParameterByName("editorConfigOverride")!! to editorConfigOverride
)
) as com.pinterest.ktlint.core.KtLint.ExperimentalParams
}

override fun invokeLint(file: File, cb: (LintError, Boolean) -> Unit) {
com.pinterest.ktlint.core.KtLint.lint(buildParams(file, cb))
}

override fun invokeFormat(file: File, cb: (LintError, Boolean) -> Unit): String {
return com.pinterest.ktlint.core.KtLint.format(buildParams(file, cb))
}
}

fun getCodeStyle(styleName: String): Any {
return try {
Class.forName("com.pinterest.ktlint.core.api.DefaultEditorConfigProperties\$CodeStyleValue").getDeclaredField(styleName).get(null)
} catch (e: ClassNotFoundException) {
(Class.forName("com.pinterest.ktlint.core.api.editorconfig.CodeStyleValue").enumConstants as Array<Enum<*>>).first {
it.name == styleName
}
}
}

fun getEditorConfigPropertyClass(): Class<*> {
return try {
Class.forName("com.pinterest.ktlint.core.api.UsesEditorConfigProperties\$EditorConfigProperty")
} catch (e: ClassNotFoundException) {
Class.forName("com.pinterest.ktlint.core.api.editorconfig.EditorConfigProperty")
}
}

@OptIn(FeatureInAlphaState::class)
fun userDataToEditorConfigOverride(userData: Map<String, String>): Any {
val defaultEditorConfigPropertiesClass = Class.forName("com.pinterest.ktlint.core.api.DefaultEditorConfigProperties")
val defaultEditorConfigProperties = defaultEditorConfigPropertiesClass.kotlin.objectInstance
val codeStyle = getCodeStyle(if (userData["android"]?.toBoolean() == true) "android" else "official")
val editorConfigOverrideClass = Class.forName("com.pinterest.ktlint.core.api.EditorConfigOverride")
val editorConfigOverride = editorConfigOverrideClass.kotlin.primaryConstructor!!.call()
val addMethod = editorConfigOverrideClass.getDeclaredMethod("add", getEditorConfigPropertyClass(), Any::class.java)
addMethod.isAccessible = true
val disabledRulesProperty = defaultEditorConfigPropertiesClass.kotlin.memberProperties.firstOrNull { it.name == "ktlintDisabledRulesProperty" }
?: defaultEditorConfigPropertiesClass.kotlin.memberProperties.first { it.name == "disabledRulesProperty" }
val codeStyleSetProperty = defaultEditorConfigPropertiesClass.kotlin.memberProperties.first { it.name == "codeStyleSetProperty" }
addMethod.invoke(editorConfigOverride, disabledRulesProperty.getter.call(defaultEditorConfigProperties), userData["disabled_rules"])
addMethod.invoke(editorConfigOverride, codeStyleSetProperty.getter.call(defaultEditorConfigProperties), codeStyle)
return editorConfigOverride
}

@OptIn(FeatureInAlphaState::class)
internal class ExperimentalParamsProviderInvocation: KtLintInvocation {
private var editorConfigPath: String? = null
private var debug: Boolean = false
private lateinit var ruleProviders: Set<Any>
private lateinit var userData: Map<String, String>
fun initialize(
editorConfigPath: String?,
ruleProviders: Set<Any>,
userData: Map<String, String>,
debug: Boolean
) {
this.editorConfigPath = editorConfigPath
this.ruleProviders = ruleProviders
this.userData = userData
this.debug = debug
}
private fun buildParams(file: File, cb: (LintError, Boolean) -> Unit): com.pinterest.ktlint.core.KtLint.ExperimentalParams {
val script = !file.name.endsWith(".kt", ignoreCase = true)
val ctor = Class.forName("com.pinterest.ktlint.core.KtLint\$ExperimentalParams").kotlin.primaryConstructor
val editorConfigOverride = userDataToEditorConfigOverride(userData)
return ctor!!.callBy(
mapOf(
ctor.findParameterByName("fileName")!! to file.absolutePath,
ctor.findParameterByName("text")!! to file.readText(),
ctor.findParameterByName("ruleProviders")!! to ruleProviders,
ctor.findParameterByName("cb")!! to cb,
ctor.findParameterByName("script")!! to script,
ctor.findParameterByName("editorConfigPath")!! to editorConfigPath,
ctor.findParameterByName("debug")!! to debug,
ctor.findParameterByName("editorConfigOverride")!! to editorConfigOverride
)
) as com.pinterest.ktlint.core.KtLint.ExperimentalParams
}

override fun invokeLint(file: File, cb: (LintError, Boolean) -> Unit) {
com.pinterest.ktlint.core.KtLint.lint(buildParams(file, cb))
}

override fun invokeFormat(file: File, cb: (LintError, Boolean) -> Unit): String {
return com.pinterest.ktlint.core.KtLint.format(buildParams(file, cb))
}
}

internal class RuleEngineInvocation : KtLintInvocation {
private lateinit var engine: Any
fun initialize(ruleProviders: Set<Any>, userData: Map<String, String>) {
val engineClass = Class.forName("com.pinterest.ktlint.core.KtLintRuleEngine")
val editorConfigOverride = userDataToEditorConfigOverride(userData)
val ctor = engineClass.kotlin.primaryConstructor
engine = ctor!!.callBy(
mapOf(
ctor.findParameterByName("ruleProviders")!! to ruleProviders,
ctor.findParameterByName("editorConfigOverride")!! to editorConfigOverride
)
)
}

override fun invokeLint(file: File, cb: (LintError, Boolean) -> Unit) {
engine::class.declaredMemberFunctions.forEach { println(it.name + " " + it.parameters.map { it.name }.joinToString(",")) }
val lintMethod = engine::class.memberFunctions.first {
it.name == "lint"
&& it.parameters.map { it.name }.containsAll(setOf("code", "filePath", "callback"))
}
lintMethod.callBy(
mapOf(
lintMethod.instanceParameter!! to engine,
lintMethod.findParameterByName("code")!! to file.readText(),
lintMethod.findParameterByName("filePath")!! to file.absoluteFile.toPath(),
lintMethod.findParameterByName("callback")!! to { le: LintError -> cb.invoke(le, false) }
)
)
}

override fun invokeFormat(file: File, cb: (LintError, Boolean) -> Unit): String {
val formatMethod = engine::class.memberFunctions.first {
it.name == "format"
&& it.parameters.map { it.name }.containsAll(setOf("code", "filePath", "callback"))
}
return formatMethod.callBy(
mapOf(
formatMethod.instanceParameter!! to engine,
formatMethod.findParameterByName("code")!! to file.readText(),
formatMethod.findParameterByName("filePath")!! to file.absoluteFile.toPath(),
formatMethod.findParameterByName("callback")!! to cb
)
) as String
}
}

internal fun selectInvocation(): KtLintInvocation {
return try {
KtLintInvocation::class.java.classLoader.loadClass("com.pinterest.ktlint.core.KtLint\$Params")
LegacyParamsInvocation()
} catch (e: Exception) {
try {
Class.forName("com.pinterest.ktlint.core.KtLintRuleEngine")
RuleEngineInvocation()
} catch (e: Exception) {
val ctor = Class.forName("com.pinterest.ktlint.core.KtLint\$ExperimentalParams").kotlin.primaryConstructor
if (ctor?.findParameterByName("ruleProviders") != null) {
ExperimentalParamsProviderInvocation()
} else {
ExperimentalParamsInvocation()
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.jlleitschuh.gradle.ktlint.worker

import java.util.ServiceLoader
import kotlin.reflect.full.memberProperties

internal fun loadRuleSetsFromClasspathWithRuleSetProvider(): Map<String, com.pinterest.ktlint.core.RuleSet> {
return ServiceLoader
.load(com.pinterest.ktlint.core.RuleSetProvider::class.java)
.associateBy {
val key = it.get().id
// Adapted from KtLint CLI module
if (key == "standard") "\u0000$key" else key
}
.mapValues { it.value.get() }
}

internal fun loadRuleSetsFromClasspathWithRuleSetProviderV2(): Map<String, Set<Any>> {
val ruleSetProviderV2Class = Class.forName("com.pinterest.ktlint.core.RuleSetProviderV2")
val idProperty = ruleSetProviderV2Class.kotlin.memberProperties.first{it.name == "id"}
val getRuleProviders = ruleSetProviderV2Class.getDeclaredMethod("getRuleProviders")
return ServiceLoader
.load(ruleSetProviderV2Class)
.associateBy {
val key = idProperty.getter.call(it) as String
// Adapted from KtLint CLI module
if (key == "standard") "\u0000$key" else key
}.mapValues {
getRuleProviders.invoke(it.value) as Set<Any>
}
}
Loading

0 comments on commit 777b6cd

Please sign in to comment.