Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add API to process a Kotlin script snippet (#1746)
* Add API so that KtLint API consumer is able to process a Kotlin script snippet without having to specify a file path Closes #1738
- Loading branch information
1 parent
7836a6c
commit 0ce631d
Showing
16 changed files
with
412 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
plugins { | ||
id("ktlint-kotlin-common") | ||
id("ktlint-publication") | ||
} | ||
|
||
dependencies { | ||
implementation(projects.ktlintCore) | ||
implementation(projects.ktlintRulesetStandard) | ||
implementation(libs.logback) | ||
|
||
testImplementation(libs.junit5) | ||
testImplementation(libs.assertj) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
POM_NAME=ktlint-api-consumer | ||
POM_ARTIFACT_ID=ktlint-api-consumer |
40 changes: 40 additions & 0 deletions
40
ktlint-api-consumer/src/main/kotlin/com/example/ktlint/api/consumer/KtlintApiConsumer.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package com.example.ktlint.api.consumer | ||
|
||
import com.example.ktlint.api.consumer.rules.KTLINT_API_CONSUMER_RULE_PROVIDERS | ||
import com.pinterest.ktlint.core.Code | ||
import com.pinterest.ktlint.core.KtLintRuleEngine | ||
import com.pinterest.ktlint.core.initKtLintKLogger | ||
import java.io.File | ||
import mu.KotlinLogging | ||
|
||
private val LOGGER = KotlinLogging.logger {}.initKtLintKLogger() | ||
|
||
public class KtlintApiConsumer { | ||
// The KtLint RuleEngine only needs to be instantiated once and can be reused in multiple invocations | ||
private val ktLintRuleEngine = KtLintRuleEngine( | ||
ruleProviders = KTLINT_API_CONSUMER_RULE_PROVIDERS, | ||
) | ||
|
||
public fun run(command: String, fileName: String) { | ||
val codeFile = Code.CodeFile( | ||
File(fileName), | ||
) | ||
|
||
when (command) { | ||
"lint" -> { | ||
ktLintRuleEngine | ||
.lint(codeFile) { | ||
LOGGER.info { "LintViolation reported by KtLint: $it" } | ||
} | ||
} | ||
"format" -> { | ||
ktLintRuleEngine | ||
.format(codeFile) | ||
.also { | ||
LOGGER.info { "Code formatted by KtLint:\n$it" } | ||
} | ||
} | ||
else -> LOGGER.error { "Unexpected argument '$command'" } | ||
} | ||
} | ||
} |
15 changes: 15 additions & 0 deletions
15
ktlint-api-consumer/src/main/kotlin/com/example/ktlint/api/consumer/Main.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import com.example.ktlint.api.consumer.KtlintApiConsumer | ||
import com.pinterest.ktlint.core.initKtLintKLogger | ||
import kotlin.system.exitProcess | ||
import mu.KotlinLogging | ||
|
||
private val LOGGER = KotlinLogging.logger {}.initKtLintKLogger() | ||
|
||
public fun main(args: Array<String>) { | ||
if (args.size != 2) { | ||
LOGGER.error { "Expected two arguments" } | ||
exitProcess(1) | ||
} | ||
|
||
KtlintApiConsumer().run(args[0], args[1]) | ||
} |
11 changes: 11 additions & 0 deletions
11
...i-consumer/src/main/kotlin/com/example/ktlint/api/consumer/rules/CustomRuleSetProvider.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package com.example.ktlint.api.consumer.rules | ||
|
||
import com.pinterest.ktlint.core.RuleProvider | ||
import com.pinterest.ktlint.ruleset.standard.IndentationRule | ||
|
||
internal val KTLINT_API_CONSUMER_RULE_PROVIDERS = setOf( | ||
// Can provide custom rules | ||
RuleProvider { NoVarRule() }, | ||
// but also reuse rules from KtLint rulesets | ||
RuleProvider { IndentationRule() }, | ||
) |
17 changes: 17 additions & 0 deletions
17
ktlint-api-consumer/src/main/kotlin/com/example/ktlint/api/consumer/rules/NoVarRule.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package com.example.ktlint.api.consumer.rules | ||
|
||
import com.pinterest.ktlint.core.Rule | ||
import com.pinterest.ktlint.core.ast.ElementType.VAR_KEYWORD | ||
import org.jetbrains.kotlin.com.intellij.lang.ASTNode | ||
|
||
public class NoVarRule : Rule("no-var") { | ||
override fun beforeVisitChildNodes( | ||
node: ASTNode, | ||
autoCorrect: Boolean, | ||
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, | ||
) { | ||
if (node.elementType == VAR_KEYWORD) { | ||
emit(node.startOffset, "Unexpected var, use val instead", false) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Ktlint API Consumer | ||
|
||
A very rudimentary example of how the Ktlint API can be used. For a more complete implementation see the `ktlint CLI` module. |
57 changes: 57 additions & 0 deletions
57
ktlint-api-consumer/src/test/kotlin/com/pinterest/ktlint/api/consumer/ApiTestRunner.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package com.pinterest.ktlint.api.consumer | ||
|
||
import com.pinterest.ktlint.core.initKtLintKLogger | ||
import java.nio.file.FileVisitResult | ||
import java.nio.file.Files | ||
import java.nio.file.Path | ||
import java.nio.file.SimpleFileVisitor | ||
import java.nio.file.attribute.BasicFileAttributes | ||
import kotlin.io.path.Path | ||
import kotlin.io.path.copyTo | ||
import kotlin.io.path.createDirectories | ||
import kotlin.io.path.relativeToOrSelf | ||
import mu.KotlinLogging | ||
|
||
private val LOGGER = KotlinLogging.logger {}.initKtLintKLogger() | ||
|
||
class ApiTestRunner(private val tempDir: Path) { | ||
fun prepareTestProject(testProjectName: String): Path { | ||
val testProjectPath = TEST_PROJECTS_PATHS.resolve(testProjectName) | ||
assert(Files.exists(testProjectPath)) { | ||
"Test project $testProjectName does not exist!" | ||
} | ||
|
||
return tempDir.resolve(testProjectName).also { testProjectPath.copyRecursively(it) } | ||
} | ||
|
||
private fun Path.copyRecursively(dest: Path) { | ||
Files.walkFileTree( | ||
this, | ||
object : SimpleFileVisitor<Path>() { | ||
override fun preVisitDirectory( | ||
dir: Path, | ||
attrs: BasicFileAttributes, | ||
): FileVisitResult { | ||
val relativeDir = dir.relativeToOrSelf(this@copyRecursively) | ||
dest.resolve(relativeDir).createDirectories() | ||
return FileVisitResult.CONTINUE | ||
} | ||
|
||
override fun visitFile( | ||
file: Path, | ||
attrs: BasicFileAttributes, | ||
): FileVisitResult { | ||
val relativeFile = file.relativeToOrSelf(this@copyRecursively) | ||
val destinationFile = dest.resolve(relativeFile) | ||
LOGGER.trace { "Copy '$relativeFile' to '$destinationFile'" } | ||
file.copyTo(destinationFile) | ||
return FileVisitResult.CONTINUE | ||
} | ||
}, | ||
) | ||
} | ||
|
||
companion object { | ||
private val TEST_PROJECTS_PATHS: Path = Path("src", "test", "resources", "api") | ||
} | ||
} |
175 changes: 175 additions & 0 deletions
175
...nt-api-consumer/src/test/kotlin/com/pinterest/ktlint/api/consumer/KtLintRuleEngineTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
package com.pinterest.ktlint.api.consumer | ||
|
||
import com.pinterest.ktlint.core.Code | ||
import com.pinterest.ktlint.core.KtLintRuleEngine | ||
import com.pinterest.ktlint.core.LintError | ||
import com.pinterest.ktlint.core.RuleProvider | ||
import com.pinterest.ktlint.ruleset.standard.IndentationRule | ||
import java.io.File | ||
import java.nio.file.Path | ||
import org.assertj.core.api.Assertions.assertThat | ||
import org.junit.jupiter.api.Nested | ||
import org.junit.jupiter.api.Test | ||
import org.junit.jupiter.api.io.TempDir | ||
|
||
/** | ||
* The KtLintRuleEngine is use by the Ktlint CLI and external API Consumers. Although most functionalities of the RuleEngine are already | ||
* tested via the Ktlint CLI Tests and normal unit tests in KtLint Core, some functionalities need additional testing from the perspective | ||
* of an API Consumer to ensure that the API is usable and stable across releases. | ||
*/ | ||
class KtLintRuleEngineTest { | ||
@Nested | ||
inner class `Lint with KtLintRuleEngine` { | ||
@Test | ||
fun `Given a file that does not contain an error`( | ||
@TempDir | ||
tempDir: Path, | ||
) { | ||
val dir = ApiTestRunner(tempDir).prepareTestProject("no-code-style-error") | ||
|
||
val ktLintRuleEngine = KtLintRuleEngine( | ||
ruleProviders = setOf( | ||
RuleProvider { IndentationRule() }, | ||
), | ||
) | ||
|
||
val lintErrors = mutableListOf<LintError>() | ||
ktLintRuleEngine.lint( | ||
code = Code.CodeFile(File("$dir/Main.kt")), | ||
callback = { lintErrors.add(it) }, | ||
) | ||
|
||
assertThat(lintErrors).isEmpty() | ||
} | ||
|
||
@Test | ||
fun `Given a kotlin code snippet that does not contain an error`() { | ||
val ktLintRuleEngine = KtLintRuleEngine( | ||
ruleProviders = setOf( | ||
RuleProvider { IndentationRule() }, | ||
), | ||
) | ||
|
||
val lintErrors = mutableListOf<LintError>() | ||
ktLintRuleEngine.lint( | ||
code = Code.CodeSnippet( | ||
""" | ||
fun main() { | ||
println("Hello world!") | ||
} | ||
""".trimIndent(), | ||
), | ||
callback = { lintErrors.add(it) }, | ||
) | ||
|
||
assertThat(lintErrors).isEmpty() | ||
} | ||
|
||
@Test | ||
fun `Givens a kotlin script code snippet that does not contain an error`() { | ||
val ktLintRuleEngine = KtLintRuleEngine( | ||
ruleProviders = setOf( | ||
RuleProvider { IndentationRule() }, | ||
), | ||
) | ||
|
||
val lintErrors = mutableListOf<LintError>() | ||
ktLintRuleEngine.lint( | ||
code = Code.CodeSnippet( | ||
""" | ||
plugins { | ||
id("foo") | ||
id("bar") | ||
} | ||
""".trimIndent(), | ||
script = true, | ||
), | ||
callback = { lintErrors.add(it) }, | ||
) | ||
|
||
assertThat(lintErrors).isEmpty() | ||
} | ||
} | ||
|
||
@Nested | ||
inner class `Format with KtLintRuleEngine` { | ||
@Test | ||
fun `Given a file that does not contain an error`( | ||
@TempDir | ||
tempDir: Path, | ||
) { | ||
val dir = ApiTestRunner(tempDir).prepareTestProject("no-code-style-error") | ||
|
||
val ktLintRuleEngine = KtLintRuleEngine( | ||
ruleProviders = setOf( | ||
RuleProvider { IndentationRule() }, | ||
), | ||
) | ||
|
||
val original = File("$dir/Main.kt").readText() | ||
|
||
val actual = ktLintRuleEngine.format( | ||
code = Code.CodeFile(File("$dir/Main.kt")), | ||
) | ||
|
||
assertThat(actual).isEqualTo(original) | ||
} | ||
|
||
@Test | ||
fun `Given a kotlin code snippet that does contain an indentation error`() { | ||
val ktLintRuleEngine = KtLintRuleEngine( | ||
ruleProviders = setOf( | ||
RuleProvider { IndentationRule() }, | ||
), | ||
) | ||
|
||
val actual = ktLintRuleEngine.format( | ||
code = Code.CodeSnippet( | ||
""" | ||
fun main() { | ||
println("Hello world!") | ||
} | ||
""".trimIndent(), | ||
), | ||
) | ||
|
||
assertThat(actual).isEqualTo( | ||
""" | ||
fun main() { | ||
println("Hello world!") | ||
} | ||
""".trimIndent(), | ||
) | ||
} | ||
|
||
@Test | ||
fun `Given a kotlin script code snippet that does contain an indentation error`() { | ||
val ktLintRuleEngine = KtLintRuleEngine( | ||
ruleProviders = setOf( | ||
RuleProvider { IndentationRule() }, | ||
), | ||
) | ||
|
||
val actual = ktLintRuleEngine.format( | ||
code = Code.CodeSnippet( | ||
""" | ||
plugins { | ||
id("foo") | ||
id("bar") | ||
} | ||
""".trimIndent(), | ||
script = true, | ||
), | ||
) | ||
|
||
assertThat(actual).isEqualTo( | ||
""" | ||
plugins { | ||
id("foo") | ||
id("bar") | ||
} | ||
""".trimIndent(), | ||
) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Ktlint API Consumer API tests | ||
|
||
The tests in this module do *not* test the Ktlint API Consumer itself. The tests verify the usage of the Ktlint API from the perspective of the API Consumer. This to ensure that the API is usable from an external perspective. |
3 changes: 3 additions & 0 deletions
3
ktlint-api-consumer/src/test/resources/api/no-code-style-error/Main.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
fun main() { | ||
println("Hello world!") | ||
} |
Oops, something went wrong.