diff --git a/CHANGELOG.md b/CHANGELOG.md index 785fca65..0e07cc9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ Please add your entries according to this format. ## Unreleased +- Add tasks `ktfmtCheckScripts` and `ktfmtFormatScripts` to check and format the `.kts` files in the project folder. + ## Version 0.21.0 _(2024-11-01)_ - KtFmt to 0.53 - AGP to 8.7.1 @@ -187,4 +189,4 @@ Please add your entries according to this format. ## Version 0.1.0 _(2020-12-22)_ -That's the first version of `ktfmt-gradle` \ No newline at end of file +That's the first version of `ktfmt-gradle` diff --git a/README.md b/README.md index 57f1fc6e..ff99bb6b 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,11 @@ By default, `ktfmt-gradle` will add two Gradle tasks to your build: Those two tasks will invoke `ktfmt` on the **whole module**. More specific tasks are avialable based on the module type. +Additionally, the plugin will create check/format tasks for top-level Kotlin script files: + +- `ktfmtCheckScripts` that will check if the top-level script files in your module is ktfmt-compliant +- `ktfmtFormatScripts` that will reformat top-level script files with ktfmt + #### Jvm/Js Modules For Jvm/Js modules, the plugin will create a check/format task for **every source set**. For example, jvm modules will diff --git a/plugin-build/plugin/src/main/java/com/ncorti/ktfmt/gradle/KtfmtPlugin.kt b/plugin-build/plugin/src/main/java/com/ncorti/ktfmt/gradle/KtfmtPlugin.kt index 9526f9e2..91c5c5b1 100644 --- a/plugin-build/plugin/src/main/java/com/ncorti/ktfmt/gradle/KtfmtPlugin.kt +++ b/plugin-build/plugin/src/main/java/com/ncorti/ktfmt/gradle/KtfmtPlugin.kt @@ -4,6 +4,7 @@ import com.ncorti.ktfmt.gradle.KtfmtAndroidUtils.applyKtfmtToAndroidProject import com.ncorti.ktfmt.gradle.KtfmtPluginUtils.EXTENSION_NAME import com.ncorti.ktfmt.gradle.KtfmtPluginUtils.TASK_NAME_CHECK import com.ncorti.ktfmt.gradle.KtfmtPluginUtils.TASK_NAME_FORMAT +import com.ncorti.ktfmt.gradle.KtfmtPluginUtils.createScriptsTasks import com.ncorti.ktfmt.gradle.KtfmtPluginUtils.createTasksForSourceSet import com.ncorti.ktfmt.gradle.tasks.KtfmtBaseTask import com.ncorti.ktfmt.gradle.util.i @@ -62,6 +63,7 @@ abstract class KtfmtPlugin : Plugin { if (project.plugins.hasPlugin("org.jetbrains.kotlin.multiplatform")) { project.logger.i("Skipping Android task creation, as KMP is applied") } else { + createScriptsTasks(project, project.projectDir, topLevelFormat, topLevelCheck) applyKtfmtToAndroidProject(project, topLevelFormat, topLevelCheck, ktfmtExtension) } } @@ -73,6 +75,7 @@ abstract class KtfmtPlugin : Plugin { private fun applyKtfmt(project: Project, ktfmtExtension: KtfmtExtension) { val extension = project.extensions.getByType(KotlinProjectExtension::class.java) + createScriptsTasks(project, project.projectDir, topLevelFormat, topLevelCheck) extension.sourceSets.all { createTasksForSourceSet( project, @@ -87,6 +90,7 @@ abstract class KtfmtPlugin : Plugin { private fun applyKtfmtToMultiplatformProject(project: Project, ktfmtExtension: KtfmtExtension) { val extension = project.extensions.getByType(KotlinMultiplatformExtension::class.java) + createScriptsTasks(project, project.projectDir, topLevelFormat, topLevelCheck) extension.sourceSets.all { val name = "kmp ${it.name}" if (it.name.startsWith("android")) { diff --git a/plugin-build/plugin/src/main/java/com/ncorti/ktfmt/gradle/KtfmtPluginUtils.kt b/plugin-build/plugin/src/main/java/com/ncorti/ktfmt/gradle/KtfmtPluginUtils.kt index 4c8649cf..b09d464b 100644 --- a/plugin-build/plugin/src/main/java/com/ncorti/ktfmt/gradle/KtfmtPluginUtils.kt +++ b/plugin-build/plugin/src/main/java/com/ncorti/ktfmt/gradle/KtfmtPluginUtils.kt @@ -63,6 +63,49 @@ internal object KtfmtPluginUtils { } } + internal fun createScriptsTasks( + project: Project, + projectDir: File, + topLevelFormat: TaskProvider, + topLevelCheck: TaskProvider, + ) { + val checkTaskName = "${TASK_NAME_CHECK}Scripts" + val formatTaskName = "${TASK_NAME_FORMAT}Scripts" + + val scriptFiles = + project + .fileTree(projectDir) + .filter { it.extension == "kts" } + .filter { it.parentFile == projectDir } + + val scriptCheckTask = + project.tasks.register(checkTaskName, KtfmtCheckTask::class.java) { + it.description = + "Run Ktfmt formatter validation for script files on project '${project.name}'" + it.setSource(scriptFiles) + it.setIncludes(KtfmtPlugin.defaultIncludes) + } + val scriptFormatTask = + project.tasks.register(formatTaskName, KtfmtFormatTask::class.java) { + it.description = "Run Ktfmt formatter for script files on project '${project.name}'" + it.setSource(scriptFiles) + it.setIncludes(KtfmtPlugin.defaultIncludes) + } + + project.tasks.withType(KotlinCompile::class.java).all { + it.mustRunAfter(scriptCheckTask, scriptFormatTask) + } + + topLevelFormat.configure { task -> task.dependsOn(scriptFormatTask) } + topLevelCheck.configure { task -> task.dependsOn(scriptCheckTask) } + + project.plugins.withType(LifecycleBasePlugin::class.java) { + project.tasks.named(LifecycleBasePlugin.CHECK_TASK_NAME).configure { task -> + task.dependsOn(scriptCheckTask) + } + } + } + private fun createCheckTask( project: Project, name: String, diff --git a/plugin-build/plugin/src/test/java/com/ncorti/ktfmt/gradle/KtfmtPluginTest.kt b/plugin-build/plugin/src/test/java/com/ncorti/ktfmt/gradle/KtfmtPluginTest.kt index bfe227ea..6e9ce4a6 100644 --- a/plugin-build/plugin/src/test/java/com/ncorti/ktfmt/gradle/KtfmtPluginTest.kt +++ b/plugin-build/plugin/src/test/java/com/ncorti/ktfmt/gradle/KtfmtPluginTest.kt @@ -32,10 +32,13 @@ class KtfmtPluginTest { assertThat(project["ktfmtCheckTest"]).isInstanceOf(KtfmtCheckTask::class.java) assertThat(project["ktfmtFormatTest"]).isInstanceOf(KtfmtFormatTask::class.java) + assertThat(project["ktfmtCheckScripts"]).isInstanceOf(KtfmtCheckTask::class.java) + assertThat(project["ktfmtFormatScripts"]).isInstanceOf(KtfmtFormatTask::class.java) + assertThat(project["ktfmtCheck"].dependencies) - .containsExactly("ktfmtCheckMain", "ktfmtCheckTest") + .containsExactly("ktfmtCheckMain", "ktfmtCheckTest", "ktfmtCheckScripts") assertThat(project["ktfmtFormat"].dependencies) - .containsExactly("ktfmtFormatMain", "ktfmtFormatTest") + .containsExactly("ktfmtFormatMain", "ktfmtFormatTest", "ktfmtFormatScripts") } @Test @@ -50,9 +53,9 @@ class KtfmtPluginTest { assertThat(project["ktfmtFormatTest"]).isInstanceOf(KtfmtFormatTask::class.java) assertThat(project["ktfmtCheck"].dependencies) - .containsExactly("ktfmtCheckMain", "ktfmtCheckTest") + .containsExactly("ktfmtCheckMain", "ktfmtCheckTest", "ktfmtCheckScripts") assertThat(project["ktfmtFormat"].dependencies) - .containsExactly("ktfmtFormatMain", "ktfmtFormatTest") + .containsExactly("ktfmtFormatMain", "ktfmtFormatTest", "ktfmtFormatScripts") } @Test @@ -71,9 +74,17 @@ class KtfmtPluginTest { .isInstanceOf(KtfmtFormatTask::class.java) assertThat(project["ktfmtCheck"].dependencies) - .containsExactly("ktfmtCheckKmpCommonMain", "ktfmtCheckKmpCommonTest") + .containsExactly( + "ktfmtCheckKmpCommonMain", + "ktfmtCheckKmpCommonTest", + "ktfmtCheckScripts", + ) assertThat(project["ktfmtFormat"].dependencies) - .containsExactly("ktfmtFormatKmpCommonMain", "ktfmtFormatKmpCommonTest") + .containsExactly( + "ktfmtFormatKmpCommonMain", + "ktfmtFormatKmpCommonTest", + "ktfmtFormatScripts", + ) } @Suppress("LongMethod") @@ -158,6 +169,7 @@ class KtfmtPluginTest { "ktfmtCheckTestFixtures", "ktfmtCheckTestFixturesRelease", "ktfmtCheckTestFixturesDebug", + "ktfmtCheckScripts", ) assertThat(project["ktfmtFormat"].dependencies) .containsExactly( @@ -173,6 +185,7 @@ class KtfmtPluginTest { "ktfmtFormatTestFixtures", "ktfmtFormatTestFixturesRelease", "ktfmtFormatTestFixturesDebug", + "ktfmtFormatScripts", ) } diff --git a/plugin-build/plugin/src/test/java/com/ncorti/ktfmt/gradle/tasks/KtfmtCheckTaskIntegrationTest.kt b/plugin-build/plugin/src/test/java/com/ncorti/ktfmt/gradle/tasks/KtfmtCheckTaskIntegrationTest.kt index 186e7588..2a0dbdce 100644 --- a/plugin-build/plugin/src/test/java/com/ncorti/ktfmt/gradle/tasks/KtfmtCheckTaskIntegrationTest.kt +++ b/plugin-build/plugin/src/test/java/com/ncorti/ktfmt/gradle/tasks/KtfmtCheckTaskIntegrationTest.kt @@ -1,6 +1,8 @@ package com.ncorti.ktfmt.gradle.tasks import com.google.common.truth.Truth.assertThat +import com.ncorti.ktfmt.gradle.testutil.appendToBuildGradle +import com.ncorti.ktfmt.gradle.testutil.createTempFile import java.io.File import org.gradle.testkit.runner.BuildResult import org.gradle.testkit.runner.GradleRunner @@ -9,7 +11,6 @@ import org.gradle.testkit.runner.TaskOutcome.FROM_CACHE import org.gradle.testkit.runner.TaskOutcome.NO_SOURCE import org.gradle.testkit.runner.TaskOutcome.SUCCESS import org.gradle.testkit.runner.TaskOutcome.UP_TO_DATE -import org.intellij.lang.annotations.Language import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.io.TempDir @@ -27,7 +28,7 @@ internal class KtfmtCheckTaskIntegrationTest { @Test fun `check task fails if there is invalid code`() { - createTempFile(content = "val answer = `") + tempDir.createTempFile(content = "val answer = `") val result = GradleRunner.create() .withProjectDir(tempDir) @@ -41,7 +42,7 @@ internal class KtfmtCheckTaskIntegrationTest { @Test fun `check task fails if there is not formatted code`() { - createTempFile(content = "val answer=42") + tempDir.createTempFile(content = "val answer=42") val result = GradleRunner.create() .withProjectDir(tempDir) @@ -56,7 +57,7 @@ internal class KtfmtCheckTaskIntegrationTest { @Test fun `check task fails if ktfmt fails to parse the code`() { // The following code will case ktfmt to fail with error: Failed to parse file - createTempFile( + tempDir.createTempFile( """ val res = when { ```` @@ -79,7 +80,7 @@ internal class KtfmtCheckTaskIntegrationTest { @Test fun `check task succeed if code is formatted`() { - createTempFile(content = "val answer = 42\n") + tempDir.createTempFile(content = "val answer = 42\n") val result = GradleRunner.create() .withProjectDir(tempDir) @@ -92,7 +93,7 @@ internal class KtfmtCheckTaskIntegrationTest { @Test fun `check task runs before compilation`() { - createTempFile(content = "val answer = 42\n") + tempDir.createTempFile(content = "val answer = 42\n") val result = GradleRunner.create() .withProjectDir(tempDir) @@ -108,7 +109,7 @@ internal class KtfmtCheckTaskIntegrationTest { @Test fun `check task prints formatted files with --info`() { - createTempFile(content = "val answer = 42\n") + tempDir.createTempFile(content = "val answer = 42\n") val result = GradleRunner.create() .withProjectDir(tempDir) @@ -123,7 +124,7 @@ internal class KtfmtCheckTaskIntegrationTest { @Test fun `format task uses configuration cache correctly`() { - createTempFile(content = "val answer = 42\n") + tempDir.createTempFile(content = "val answer = 42\n") GradleRunner.create() .withProjectDir(tempDir) .withPluginClasspath() @@ -142,8 +143,8 @@ internal class KtfmtCheckTaskIntegrationTest { @Test fun `check task validates all the file with a failure`() { - createTempFile(content = "val answer = `\n", fileName = "File1.kt") - createTempFile(content = "val answer = 42\n", fileName = "File2.kt") + tempDir.createTempFile(content = "val answer = `\n", fileName = "File1.kt") + tempDir.createTempFile(content = "val answer = 42\n", fileName = "File2.kt") val result = GradleRunner.create() @@ -159,8 +160,8 @@ internal class KtfmtCheckTaskIntegrationTest { @Test fun `check task skips a file if with --include-only`() { - createTempFile(content = "val answer = `\n", fileName = "File1.kt") - val file2 = createTempFile(content = "val answer = 42\n", fileName = "File2.kt") + tempDir.createTempFile(content = "val answer = `\n", fileName = "File1.kt") + val file2 = tempDir.createTempFile(content = "val answer = 42\n", fileName = "File2.kt") val result = GradleRunner.create() @@ -183,7 +184,10 @@ internal class KtfmtCheckTaskIntegrationTest { @ValueSource(ints = [10, 15, 30, 50, 100, 1000]) fun `check task can check the formatting of multiple files`(n: Int) { repeat(n) { index -> - createTempFile(content = "val answer${index} = 42\n", fileName = "TestFile$index.kt") + tempDir.createTempFile( + content = "val answer${index} = 42\n", + fileName = "TestFile$index.kt", + ) } val result = GradleRunner.create() @@ -198,7 +202,7 @@ internal class KtfmtCheckTaskIntegrationTest { @Test fun `check task is cacheable`() { - createTempFile(content = "val answer = 42\n") + tempDir.createTempFile(content = "val answer = 42\n") var result: BuildResult? = null repeat(2) { @@ -215,17 +219,17 @@ internal class KtfmtCheckTaskIntegrationTest { @Test fun `check task should be up-to-date when invoked twice with multiple different sized sourceSets`() { - createTempFile( + tempDir.createTempFile( content = "val answer = 42\n", fileName = "SrcFile.kt", path = "src/main/java", ) - createTempFile( + tempDir.createTempFile( content = "val answer = 42\n", fileName = "TestFile.kt", path = "src/test/java", ) - createTempFile( + tempDir.createTempFile( content = "val answer = 42\n", fileName = "TestFile2.kt", path = "src/test/java", @@ -254,7 +258,7 @@ internal class KtfmtCheckTaskIntegrationTest { @Test fun `check task is configuration cache compatible`() { - createTempFile(content = "val answer = 42\n") + tempDir.createTempFile(content = "val answer = 42\n") var result: BuildResult? = null repeat(2) { @@ -271,19 +275,16 @@ internal class KtfmtCheckTaskIntegrationTest { @Test fun `custom formatCheck task should be compatible with configuration cache`() { - createTempFile(content = "val answer = 42\n") - - tempDir - .resolve("build.gradle.kts") - .appendText( - """ - | - |tasks.register("customFormatCheck") { - | source = fileTree("src/main/java") - |} - """ - .trimMargin() - ) + tempDir.createTempFile(content = "val answer = 42\n") + + tempDir.appendToBuildGradle( + """ + |tasks.register("customFormatCheck") { + | source = fileTree("src/main/java") + |} + """ + .trimMargin() + ) GradleRunner.create() .withProjectDir(tempDir) @@ -294,22 +295,18 @@ internal class KtfmtCheckTaskIntegrationTest { @Test fun `check task should detect the source and test files in a flattened project structure`() { - appendToBuildGradle( + tempDir.appendToBuildGradle( """ |kotlin { - | sourceSets.main { - | kotlin.setSrcDirs(listOf("src")) - | } - | sourceSets.test { - | kotlin.setSrcDirs(listOf("test")) - | } + | sourceSets.main { kotlin.setSrcDirs(listOf("src")) } + | sourceSets.test { kotlin.setSrcDirs(listOf("test")) } |} """ .trimMargin() ) - createTempFile("val answer = 42\n", path = "src/someFolder") - createTempFile("val answer = 42\n", path = "test/someOtherFolder") + tempDir.createTempFile("val answer = 42\n", path = "src/someFolder") + tempDir.createTempFile("val answer = 42\n", path = "test/someOtherFolder") val result = GradleRunner.create() @@ -324,18 +321,14 @@ internal class KtfmtCheckTaskIntegrationTest { @Test fun `check task should by default ignore sourceSets in the build folder`() { - appendToBuildGradle( + tempDir.appendToBuildGradle( """ - |kotlin { - | sourceSets.main { - | kotlin.srcDirs("build/main") - | } - |} + |kotlin { sourceSets.main { kotlin.srcDirs("build/main") } } """ .trimMargin() ) - createTempFile(content = "val answer=42\n", path = "build/main") + tempDir.createTempFile(content = "val answer=42\n", path = "build/main") val result = GradleRunner.create() @@ -350,22 +343,16 @@ internal class KtfmtCheckTaskIntegrationTest { @Test fun `check task should not ignore sourceSets in build folder when a custom exclusion pattern is specified`() { - appendToBuildGradle( + tempDir.appendToBuildGradle( """ - |kotlin { - | sourceSets.main { - | kotlin.srcDirs("build/generated") - | } - |} + |kotlin { sourceSets.main { kotlin.srcDirs("build/generated") } } | - |ktfmt{ - | srcSetPathExclusionPattern.set(Regex("customRules.*")) - |} + |ktfmt { srcSetPathExclusionPattern.set(Regex("customRules.*")) } """ .trimMargin() ) - createTempFile(content = "val answer=42\n", path = "build/generated/main") + tempDir.createTempFile(content = "val answer=42\n", path = "build/generated/main") val result = GradleRunner.create() @@ -376,20 +363,19 @@ internal class KtfmtCheckTaskIntegrationTest { .buildAndFail() assertThat(result.task(":ktfmtCheckMain")?.outcome).isEqualTo(FAILED) + assertThat(result.output).containsMatch("Invalid formatting for: .*TestFile.kt") } @Test fun `check task should ignore the main sourceSets when specified as exclusion pattern`() { - appendToBuildGradle( + tempDir.appendToBuildGradle( """ - |ktfmt{ - | srcSetPathExclusionPattern.set(Regex(".*[\\\\/]main[\\\\/].*")) - |} + |ktfmt { srcSetPathExclusionPattern.set(Regex(".*[\\\\/]main[\\\\/].*")) } """ .trimMargin() ) - createTempFile(content = "val answer=42\n") + tempDir.createTempFile(content = "val answer=42\n") val result = GradleRunner.create() @@ -402,21 +388,37 @@ internal class KtfmtCheckTaskIntegrationTest { assertThat(result.task(":ktfmtCheckMain")?.outcome).isEqualTo(NO_SOURCE) } - private fun appendToBuildGradle(content: String) { - tempDir.resolve("build.gradle.kts").apply { - appendText(System.lineSeparator()) - appendText(content) - } + @Test + fun `check scripts task should validate top-level script file`() { + tempDir.createTempFile(content = "val answer=42\n", fileName = "TestFile.kts", path = "") + + val result = + GradleRunner.create() + .withProjectDir(tempDir) + .withPluginClasspath() + .withArguments("ktfmtCheckScripts") + .buildAndFail() + + assertThat(result.task(":ktfmtCheckScripts")?.outcome).isEqualTo(FAILED) + assertThat(result.output).containsMatch("Invalid formatting for: .*TestFile.kts") } - private fun createTempFile( - @Language("kotlin") content: String, - fileName: String = "TestFile.kt", - path: String = "src/main/java", - ): File = - tempDir.resolve(path).resolve(fileName).apply { - parentFile.mkdirs() - createNewFile() - writeText(content) - } + @Test + fun `check scripts task should ignore non top-level script files`() { + tempDir.createTempFile( + content = "val answer=42\n", + fileName = "TestFile.kts", + path = "src/main/java", + ) + + val result = + GradleRunner.create() + .withProjectDir(tempDir) + .withPluginClasspath() + .withArguments("ktfmtCheckScripts") + .forwardOutput() + .build() + + assertThat(result.task(":ktfmtCheckScripts")?.outcome).isEqualTo(SUCCESS) + } } diff --git a/plugin-build/plugin/src/test/java/com/ncorti/ktfmt/gradle/tasks/KtfmtFormatTaskIntegrationTest.kt b/plugin-build/plugin/src/test/java/com/ncorti/ktfmt/gradle/tasks/KtfmtFormatTaskIntegrationTest.kt index 76d8b83b..2e4b5ed5 100644 --- a/plugin-build/plugin/src/test/java/com/ncorti/ktfmt/gradle/tasks/KtfmtFormatTaskIntegrationTest.kt +++ b/plugin-build/plugin/src/test/java/com/ncorti/ktfmt/gradle/tasks/KtfmtFormatTaskIntegrationTest.kt @@ -1,13 +1,14 @@ package com.ncorti.ktfmt.gradle.tasks import com.google.common.truth.Truth.assertThat +import com.ncorti.ktfmt.gradle.testutil.appendToBuildGradle +import com.ncorti.ktfmt.gradle.testutil.createTempFile import java.io.File import org.gradle.testkit.runner.GradleRunner import org.gradle.testkit.runner.TaskOutcome.FAILED import org.gradle.testkit.runner.TaskOutcome.NO_SOURCE import org.gradle.testkit.runner.TaskOutcome.SUCCESS import org.gradle.testkit.runner.TaskOutcome.UP_TO_DATE -import org.intellij.lang.annotations.Language import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.io.TempDir @@ -25,7 +26,7 @@ internal class KtfmtFormatTaskIntegrationTest { @Test fun `format task fails if there is invalid code`() { - createTempFile(content = "val answer = `") + tempDir.createTempFile(content = "val answer = `") val result = GradleRunner.create() .withProjectDir(tempDir) @@ -39,7 +40,7 @@ internal class KtfmtFormatTaskIntegrationTest { @Test fun `format formats correctly`() { - val tempFile = createTempFile(content = "val answer=42") + val tempFile = tempDir.createTempFile(content = "val answer=42") val result = GradleRunner.create() .withProjectDir(tempDir) @@ -53,7 +54,7 @@ internal class KtfmtFormatTaskIntegrationTest { @Test fun `format task succeed if code is formatted`() { - createTempFile(content = "val answer = 42\n") + tempDir.createTempFile(content = "val answer = 42\n") val result = GradleRunner.create() .withProjectDir(tempDir) @@ -66,7 +67,7 @@ internal class KtfmtFormatTaskIntegrationTest { @Test fun `format task is up to date after subsequent execution`() { - createTempFile(content = "val answer = 42\n") + tempDir.createTempFile(content = "val answer = 42\n") var result = GradleRunner.create() .withProjectDir(tempDir) @@ -88,7 +89,7 @@ internal class KtfmtFormatTaskIntegrationTest { @Test fun `format task is up to date after subsequent execution when formatting`() { - createTempFile(content = "val answer=42") + tempDir.createTempFile(content = "val answer=42") var result = GradleRunner.create() .withProjectDir(tempDir) @@ -117,7 +118,7 @@ internal class KtfmtFormatTaskIntegrationTest { @Test fun `format task is up to executed again after edit`() { - val tempFile = createTempFile(content = "val answer = 42\n") + val tempFile = tempDir.createTempFile(content = "val answer = 42\n") var result = GradleRunner.create() .withProjectDir(tempDir) @@ -152,7 +153,7 @@ internal class KtfmtFormatTaskIntegrationTest { @Test fun `format task prints formatted files with --info`() { - createTempFile(content = "val answer=42\n") + tempDir.createTempFile(content = "val answer=42\n") val result = GradleRunner.create() .withProjectDir(tempDir) @@ -167,7 +168,7 @@ internal class KtfmtFormatTaskIntegrationTest { @Test fun `format task uses configuration cache correctly`() { - createTempFile(content = "val answer = 42\n") + tempDir.createTempFile(content = "val answer = 42\n") GradleRunner.create() .withProjectDir(tempDir) .withPluginClasspath() @@ -186,8 +187,8 @@ internal class KtfmtFormatTaskIntegrationTest { @Test fun `format task reformats all the file even with a failure`() { - val file1 = createTempFile(content = "val answer = `", fileName = "File1.kt") - val file2 = createTempFile(content = "val answer=42", fileName = "File2.kt") + val file1 = tempDir.createTempFile(content = "val answer = `", fileName = "File1.kt") + val file2 = tempDir.createTempFile(content = "val answer=42", fileName = "File2.kt") val result = GradleRunner.create() @@ -207,7 +208,7 @@ internal class KtfmtFormatTaskIntegrationTest { @Test fun `format task runs before compilation`() { - createTempFile(content = "val answer = 42\n") + tempDir.createTempFile(content = "val answer = 42\n") val result = GradleRunner.create() .withProjectDir(tempDir) @@ -223,8 +224,8 @@ internal class KtfmtFormatTaskIntegrationTest { @Test fun `format task skips a file if with --include-only`() { - createTempFile(content = "val answer = `\n", fileName = "File1.kt") - val file2 = createTempFile(content = "val answer=42\n", fileName = "File2.kt") + tempDir.createTempFile(content = "val answer = `\n", fileName = "File1.kt") + val file2 = tempDir.createTempFile(content = "val answer=42\n", fileName = "File2.kt") val result = GradleRunner.create() @@ -248,7 +249,10 @@ internal class KtfmtFormatTaskIntegrationTest { @ValueSource(ints = [10, 15, 30, 50, 100, 1000]) fun `format task can format multiple files`(n: Int) { repeat(n) { index -> - createTempFile(content = "val answer${index}=42\n", fileName = "TestFile$index.kt") + tempDir.createTempFile( + content = "val answer${index}=42\n", + fileName = "TestFile$index.kt", + ) } val result = GradleRunner.create() @@ -263,19 +267,16 @@ internal class KtfmtFormatTaskIntegrationTest { @Test fun `custom format task should be compatible with configuration cache`() { - createTempFile(content = "val answer = 42\n") - - tempDir - .resolve("build.gradle.kts") - .appendText( - """ - | - |tasks.register("customFormatTask") { - | source = fileTree("src/main/java") - |} - """ - .trimMargin() - ) + tempDir.createTempFile(content = "val answer = 42\n") + + tempDir.appendToBuildGradle( + """ + |tasks.register("customFormatTask") { + | source = fileTree("src/main/java") + |} + """ + .trimMargin() + ) GradleRunner.create() .withProjectDir(tempDir) @@ -287,7 +288,7 @@ internal class KtfmtFormatTaskIntegrationTest { @Test fun `should format the files in kotlinLang style with a 4 space indentation`() { val file = - createTempFile( + tempDir.createTempFile( content = """ |fun someFun(){ @@ -298,7 +299,7 @@ internal class KtfmtFormatTaskIntegrationTest { .trimMargin() ) - appendToBuildGradle("ktfmt { kotlinLangStyle() }") + tempDir.appendToBuildGradle("ktfmt { kotlinLangStyle() }") GradleRunner.create() .withProjectDir(tempDir) @@ -320,7 +321,7 @@ internal class KtfmtFormatTaskIntegrationTest { @Test fun `should format the files in googleStyle style with a 2 space indentation`() { val file = - createTempFile( + tempDir.createTempFile( content = """ |fun someFun(){ @@ -331,7 +332,7 @@ internal class KtfmtFormatTaskIntegrationTest { .trimMargin() ) - appendToBuildGradle("ktfmt { googleStyle() }") + tempDir.appendToBuildGradle("ktfmt { googleStyle() }") GradleRunner.create() .withProjectDir(tempDir) @@ -353,22 +354,18 @@ internal class KtfmtFormatTaskIntegrationTest { @Test fun `format task should detect the source and test files in a flattened project structure and format them`() { - appendToBuildGradle( + tempDir.appendToBuildGradle( """ |kotlin { - | sourceSets.main { - | kotlin.setSrcDirs(listOf("src")) - | } - | sourceSets.test { - | kotlin.setSrcDirs(listOf("test")) - | } + | sourceSets.main { kotlin.setSrcDirs(listOf("src")) } + | sourceSets.test { kotlin.setSrcDirs(listOf("test")) } |} """ .trimMargin() ) - val sourceFile = createTempFile("val answer = 42\n", path = "src/someFolder") - val testFile = createTempFile("val answer = 42\n", path = "test/someOtherFolder") + val sourceFile = tempDir.createTempFile("val answer = 42\n", path = "src/someFolder") + val testFile = tempDir.createTempFile("val answer = 42\n", path = "test/someOtherFolder") val result = GradleRunner.create() @@ -386,18 +383,14 @@ internal class KtfmtFormatTaskIntegrationTest { @Test fun `format task should by default not format sourceSets in the build folder`() { - appendToBuildGradle( + tempDir.appendToBuildGradle( """ - |kotlin { - | sourceSets.main { - | kotlin.srcDirs("build/main") - | } - |} + |kotlin { sourceSets.main { kotlin.srcDirs("build/main") } } """ .trimMargin() ) - val file = createTempFile(content = "val answer=42\n", path = "build/main") + val file = tempDir.createTempFile(content = "val answer=42\n", path = "build/main") val result = GradleRunner.create() @@ -414,22 +407,17 @@ internal class KtfmtFormatTaskIntegrationTest { @Test fun `format task should not ignore sourceSets in build folder when a custom exclusion pattern is specified`() { - appendToBuildGradle( + tempDir.appendToBuildGradle( """ - |kotlin { - | sourceSets.main { - | kotlin.srcDirs("build/generated") - | } - |} + |kotlin { sourceSets.main { kotlin.srcDirs("build/generated") } } | - |ktfmt{ - | srcSetPathExclusionPattern.set(Regex("customRules.*")) - |} + |ktfmt { srcSetPathExclusionPattern.set(Regex("customRules.*")) } """ .trimMargin() ) - val file = createTempFile(content = "val answer=42\n", path = "build/generated/main") + val file = + tempDir.createTempFile(content = "val answer=42\n", path = "build/generated/main") GradleRunner.create() .withProjectDir(tempDir) @@ -444,16 +432,14 @@ internal class KtfmtFormatTaskIntegrationTest { @Test fun `format task should ignore the main sourceSets when specified as exclusion pattern`() { - appendToBuildGradle( + tempDir.appendToBuildGradle( """ - |ktfmt{ - | srcSetPathExclusionPattern.set(Regex(".*[\\\\/]main[\\\\/].*")) - |} + |ktfmt { srcSetPathExclusionPattern.set(Regex(".*[\\\\/]main[\\\\/].*")) } """ .trimMargin() ) - createTempFile(content = "val answer=42\n") + tempDir.createTempFile(content = "val answer=42\n") val result = GradleRunner.create() @@ -466,21 +452,56 @@ internal class KtfmtFormatTaskIntegrationTest { assertThat(result.task(":ktfmtFormatMain")?.outcome).isEqualTo(NO_SOURCE) } - private fun createTempFile( - @Language("kotlin") content: String, - fileName: String = "TestFile.kt", - path: String = "src/main/java", - ): File = - tempDir.resolve(path).resolve(fileName).apply { - parentFile.mkdirs() - createNewFile() - writeText(content) - } + @Test + fun `format scripts task should fail if top-level script file could not be parsed`() { + val scriptFile = + tempDir.createTempFile(content = "val answer=\n", fileName = "my.kts", path = "") - private fun appendToBuildGradle(content: String) { - tempDir.resolve("build.gradle.kts").apply { - appendText(System.lineSeparator()) - appendText(content) - } + val result = + GradleRunner.create() + .withProjectDir(tempDir) + .withPluginClasspath() + .withArguments("ktfmtFormatScripts") + .forwardOutput() + .buildAndFail() + + val actual = scriptFile.readText() + assertThat(actual).isEqualTo("val answer=\n") + assertThat(result.task(":ktfmtFormatScripts")?.outcome).isEqualTo(FAILED) + } + + @Test + fun `format scripts task should format top-level script file`() { + val scriptFile = + tempDir.createTempFile(content = "val answer=42\n", fileName = "my.kts", path = "") + + val result = + GradleRunner.create() + .withProjectDir(tempDir) + .withPluginClasspath() + .withArguments("ktfmtFormatScripts") + .forwardOutput() + .build() + + val actual = scriptFile.readText() + assertThat(actual).isEqualTo("val answer = 42\n") + assertThat(result.task(":ktfmtFormatScripts")?.outcome).isEqualTo(SUCCESS) + } + + @Test + fun `format scripts task should not format non top-level script file`() { + val scriptFile = tempDir.createTempFile(content = "val answer=42\n", fileName = "my.kts") + + val result = + GradleRunner.create() + .withProjectDir(tempDir) + .withPluginClasspath() + .withArguments("ktfmtFormatScripts") + .forwardOutput() + .build() + + val actual = scriptFile.readText() + assertThat(actual).isEqualTo("val answer=42\n") + assertThat(result.task(":ktfmtFormatScripts")?.outcome).isEqualTo(SUCCESS) } } diff --git a/plugin-build/plugin/src/test/java/com/ncorti/ktfmt/gradle/testutil/File.kt b/plugin-build/plugin/src/test/java/com/ncorti/ktfmt/gradle/testutil/File.kt new file mode 100644 index 00000000..7e00810f --- /dev/null +++ b/plugin-build/plugin/src/test/java/com/ncorti/ktfmt/gradle/testutil/File.kt @@ -0,0 +1,23 @@ +package com.ncorti.ktfmt.gradle.testutil + +import java.io.File +import org.intellij.lang.annotations.Language + +fun File.appendToBuildGradle(content: String) { + resolve("build.gradle.kts").apply { + appendText(System.lineSeparator()) + appendText(content.ensureLineSeparator()) + appendText(System.lineSeparator()) + } +} + +fun File.createTempFile( + @Language("kotlin") content: String, + fileName: String = "TestFile.kt", + path: String = "src/main/java", +): File = + resolve(path).resolve(fileName).apply { + parentFile.mkdirs() + createNewFile() + writeText(content) + } diff --git a/plugin-build/plugin/src/test/java/com/ncorti/ktfmt/gradle/testutil/String.kt b/plugin-build/plugin/src/test/java/com/ncorti/ktfmt/gradle/testutil/String.kt new file mode 100644 index 00000000..206a3b2c --- /dev/null +++ b/plugin-build/plugin/src/test/java/com/ncorti/ktfmt/gradle/testutil/String.kt @@ -0,0 +1,3 @@ +package com.ncorti.ktfmt.gradle.testutil + +fun String.ensureLineSeparator(): String = replace(Regex("\r|\n|\r\n}"), System.lineSeparator()) diff --git a/plugin-build/plugin/src/test/resources/jvmProject/build.gradle.kts b/plugin-build/plugin/src/test/resources/jvmProject/build.gradle.kts index f28040ef..40728417 100644 --- a/plugin-build/plugin/src/test/resources/jvmProject/build.gradle.kts +++ b/plugin-build/plugin/src/test/resources/jvmProject/build.gradle.kts @@ -3,6 +3,6 @@ plugins { id("com.ncorti.ktfmt.gradle") } -repositories { - mavenCentral() -} \ No newline at end of file +ktfmt { kotlinLangStyle() } + +repositories { mavenCentral() }