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 KtLint baseline support #475

Merged
merged 4 commits into from
Jun 1, 2021
Merged
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
6 changes: 6 additions & 0 deletions .github/workflows/build-and-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ jobs:
${{ runner.os }}-gradle-wrapper-
- name: Build plugin
run: ./plugin/gradlew -p ./plugin build --no-daemon
- name: Save test results
uses: actions/upload-artifact@v2
if: failure()
with:
name: test-results
path: plugin/build/reports/tests/test/
- name: Check plugin codestyle
run: ./plugin/gradlew -p ./plugin ktlintCheck --no-daemon

Expand Down
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/).

## [10.1.0-SNAPSHOT] Unreleased
### Added
- ?
- Baseline support ([#414](https://github.com/JLLeitschuh/ktlint-gradle/issues/414))

Limitations:
- Format tasks ignore baseline
- One baseline file per-Gradle project (module)

### Changed
- Updated Gradle to `6.8.3` version
- Updated default KtLint version to `0.41.0`
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ The assumption being that you would not want to lint code you weren't compiling.
- [Simple setup](#simple-setup)
- [Using new plugin API](#using-new-plugin-api)
- [How to apply to all subprojects](#applying-to-subprojects)
- [Baseline support](#baseline-support)
- [Testing KtLint snapshots](#testing-ktlint-snapshots)
- [Intellij IDEA plugin](#intellij-idea-only-plugin)
- [Simple setup](#idea-plugin-simple-setup)
Expand Down Expand Up @@ -173,6 +174,12 @@ subprojects {
```
</details>

#### Baseline support

Plugin supports KtLint baseline with following limitations:
- Format tasks ignore baseline. See [#1072](https://github.com/pinterest/ktlint/issues/1072) KtLint issue for more details.
- One baseline file is generated per one Gradle project (module).

#### Testing KtLint snapshots

To test KtLint snapshots add following configuration into project build script (latest KtLint snapshot version name
Expand Down Expand Up @@ -261,6 +268,7 @@ ktlint {
enableExperimentalRules = true
additionalEditorconfigFile = file("/some/additional/.editorconfig")
disabledRules = ["final-newline"]
baseline = file("my-project-ktlint-baseline.xml")
reporters {
reporter "plain"
reporter "checkstyle"
Expand Down Expand Up @@ -311,6 +319,7 @@ configure<org.jlleitschuh.gradle.ktlint.KtlintExtension> {
enableExperimentalRules.set(true)
additionalEditorconfigFile.set(file("/some/additional/.editorconfig"))
disabledRules.set(setOf("final-newline"))
baseline.set(file("my-project-ktlint-baseline.xml"))
reporters {
reporter(ReporterType.PLAIN)
reporter(ReporterType.CHECKSTYLE)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ internal const val KTLINT_RULESET_CONFIGURATION_NAME = "ktlintRuleset"
internal const val KTLINT_RULESET_CONFIGURATION_DESCRIPTION = "All ktlint rulesets dependencies"
internal const val KTLINT_REPORTER_CONFIGURATION_NAME = "ktlintReporter"
internal const val KTLINT_REPORTER_CONFIGURATION_DESCRIPTION = "All ktlint custom reporters dependencies"
internal const val KTLINT_BASELINE_REPORTER_CONFIGURATION_NAME = "ktlintBaselineReporter"
internal const val KTLINT_BASELINE_REPORTER_CONFIGURATION_DESCRIPTION =
"Provides KtLint baseline reporter required to generate baseline file"

internal fun createKtlintConfiguration(target: Project, extension: KtlintExtension) =
target.configurations.maybeCreate(KTLINT_CONFIGURATION_NAME).apply {
Expand Down Expand Up @@ -80,6 +83,38 @@ internal fun createKtLintReporterConfiguration(
}
}

internal fun createKtLintBaselineReporterConfiguration(
target: Project,
extension: KtlintExtension,
ktLintConfiguration: Configuration
) = target
.configurations
.maybeCreate(KTLINT_BASELINE_REPORTER_CONFIGURATION_NAME)
.apply {
description = KTLINT_BASELINE_REPORTER_CONFIGURATION_DESCRIPTION
ensureConsistencyWith(target, ktLintConfiguration)

withDependencies {
dependencies.addLater(
target.provider {
val ktlintVersion = extension.version.get()
// Baseline reporter is only available starting 0.41.0 release
if (SemVer.parse(ktlintVersion) >= SemVer(0, 41, 0)) {
target.dependencies.create(
"com.pinterest.ktlint:ktlint-reporter-baseline:${extension.version.get()}"
)
} else {
// Adding fake plain reporter as addLater() does not accept `null` value
// Generate baseline tasks anyway will not run on KtLint versions < 0.41.0
target.dependencies.create(
"com.pinterest.ktlint:ktlint-reporter-plain:${extension.version.get()}"
)
}
}
)
}
}

private fun Configuration.ensureConsistencyWith(
target: Project,
otherConfiguration: Configuration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import groovy.lang.Closure
import org.gradle.api.Action
import org.gradle.api.NamedDomainObjectContainer
import org.gradle.api.file.ConfigurableFileTree
import org.gradle.api.file.ProjectLayout
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property
Expand All @@ -21,6 +22,7 @@ import org.jlleitschuh.gradle.ktlint.reporter.ReporterType
open class KtlintExtension
internal constructor(
objectFactory: ObjectFactory,
projectLayout: ProjectLayout,
customReportersContainer: NamedDomainObjectContainer<CustomReporter>,
private val filterTargetApplier: FilterApplier,
kotlinScriptAdditionalPathApplier: KotlinScriptAdditionalPathApplier
Expand Down Expand Up @@ -103,6 +105,18 @@ internal constructor(
set(emptySet())
}

/**
* Baseline file location.
*
* Default location is `<projectDir>/config/ktlint/baseline.xml`.
*
* @since KtLint `0.41.0`
*/
val baseline: RegularFileProperty = objectFactory.fileProperty()
.convention(
projectLayout.projectDirectory.dir("config").dir("ktlint").file("baseline.xml")
)

private val kscriptExtension = KScriptExtension(kotlinScriptAdditionalPathApplier)

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jlleitschuh.gradle.ktlint.android.applyKtLintToAndroid
import org.jlleitschuh.gradle.ktlint.tasks.GenerateReportsTask
import org.jlleitschuh.gradle.ktlint.tasks.KtLintCheckTask

/**
* Plugin that provides a wrapper over the `ktlint` project.
Expand All @@ -23,6 +24,7 @@ open class KtlintPlugin : Plugin<Project> {

holder.addKtLintTasksToKotlinPlugin()
holder.addKotlinScriptTasks()
holder.addGenerateBaselineTask()
holder.addGitHookTasks()
}

Expand Down Expand Up @@ -135,6 +137,13 @@ open class KtlintPlugin : Plugin<Project> {
addGenerateReportsTaskToProjectMetaFormatTask(generateReportsFormatTask)
}

private fun PluginHolder.addGenerateBaselineTask() {
createGenerateBaselineTask(
this,
target.tasks.withType(KtLintCheckTask::class.java)
)
}

internal class PluginHolder(
val target: Project
) {
Expand All @@ -157,6 +166,11 @@ open class KtlintPlugin : Plugin<Project> {
val ktlintConfiguration = createKtlintConfiguration(target, extension)
val ktlintRulesetConfiguration = createKtlintRulesetConfiguration(target, ktlintConfiguration)
val ktlintReporterConfiguration = createKtLintReporterConfiguration(target, extension, ktlintConfiguration)
val ktlintBaselineReporterConfiguration = createKtLintBaselineReporterConfiguration(
target,
extension,
ktlintConfiguration
)
val loadReportersTask = createLoadReportersTask(this)
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package org.jlleitschuh.gradle.ktlint

import org.gradle.api.file.FileTree
import org.gradle.api.tasks.TaskCollection
import org.gradle.api.tasks.TaskProvider
import org.gradle.language.base.plugins.LifecycleBasePlugin
import org.jlleitschuh.gradle.ktlint.tasks.BaseKtLintCheckTask
import org.jlleitschuh.gradle.ktlint.tasks.GenerateBaselineTask
import org.jlleitschuh.gradle.ktlint.tasks.GenerateReportsTask
import org.jlleitschuh.gradle.ktlint.tasks.KtLintCheckTask
import org.jlleitschuh.gradle.ktlint.tasks.KtLintFormatTask
Expand Down Expand Up @@ -159,4 +161,33 @@ private fun <T : BaseKtLintCheckTask> GenerateReportsTask.commonConfiguration(
ignoreFailures.set(pluginHolder.extension.ignoreFailures)
verbose.set(pluginHolder.extension.verbose)
ktLintVersion.set(pluginHolder.extension.version)
@Suppress("UnstableApiUsage")
baseline.set(
pluginHolder.extension.baseline
.flatMap {
if (it.asFile.exists()) {
pluginHolder.target.objects.fileProperty().apply { set(it.asFile) }
} else {
pluginHolder.target.objects.fileProperty()
}
}
)
}

internal fun createGenerateBaselineTask(
pluginHolder: KtlintPlugin.PluginHolder,
lintTasks: TaskCollection<out BaseKtLintCheckTask>
): TaskProvider<GenerateBaselineTask> = pluginHolder.target.registerTask(
GenerateBaselineTask.NAME
) {
description = GenerateBaselineTask.DESCRIPTION
group = HELP_GROUP

dependsOn(lintTasks)

ktLintClasspath.setFrom(pluginHolder.ktlintConfiguration)
baselineReporterClasspath.setFrom(pluginHolder.ktlintBaselineReporterConfiguration)
discoveredErrors.from(lintTasks.map { it.discoveredErrors })
ktLintVersion.set(pluginHolder.extension.version)
baselineFile.set(pluginHolder.extension.baseline)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package org.jlleitschuh.gradle.ktlint.tasks

import net.swiftzer.semver.SemVer
import org.gradle.api.DefaultTask
import org.gradle.api.Task
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.ProjectLayout
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.specs.Spec
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
import org.gradle.workers.WorkerExecutor
import org.jlleitschuh.gradle.ktlint.worker.GenerateBaselineWorkAction
import javax.inject.Inject

/**
* Generates KtLint baseline file.
*
* If baseline file is already exists - it will be overwritten.
*/
@CacheableTask
abstract class GenerateBaselineTask @Inject constructor(
private val workerExecutor: WorkerExecutor,
private val projectLayout: ProjectLayout
) : DefaultTask() {
@get:Classpath
internal abstract val ktLintClasspath: ConfigurableFileCollection

@get:Classpath
internal abstract val baselineReporterClasspath: ConfigurableFileCollection

@get:PathSensitive(PathSensitivity.RELATIVE)
@get:InputFiles
internal abstract val discoveredErrors: ConfigurableFileCollection

@get:Input
internal abstract val ktLintVersion: Property<String>

@get:OutputFile
abstract val baselineFile: RegularFileProperty

final override fun onlyIf(spec: Spec<in Task>) {
super.onlyIf(spec)
}

init {
onlyIf {
val isEnabled = SemVer.parse(ktLintVersion.get()) >= SemVer(0, 41, 0)
if (!isEnabled) logger.warn("Generate baseline only works starting from KtLint 0.41.0 version")
isEnabled
}
}

@Suppress("UnstableApiUsage")
@TaskAction
fun generateBaseline() {
// Classloader isolation is enough here as we just want to use some classes from KtLint classpath
// to get errors and generate files/console reports. No KtLint main object is initialized/used in this case.
val queue = workerExecutor.classLoaderIsolation { spec ->
spec.classpath.from(ktLintClasspath, baselineReporterClasspath)
}

queue.submit(GenerateBaselineWorkAction::class.java) { param ->
param.discoveredErrors.setFrom(discoveredErrors)
param.ktLintVersion.set(ktLintVersion)
param.baselineFile.set(baselineFile)
param.projectDirectory.set(projectLayout.projectDirectory)
}
}

companion object {
const val NAME = "ktlintGenerateBaseline"
const val DESCRIPTION = "Generates KtLint baseline file"
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package org.jlleitschuh.gradle.ktlint.tasks

import net.swiftzer.semver.SemVer
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.ProjectLayout
Expand All @@ -12,6 +14,7 @@ import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
Expand All @@ -34,7 +37,7 @@ import javax.inject.Inject
@CacheableTask
abstract class GenerateReportsTask @Inject constructor(
private val workerExecutor: WorkerExecutor,
projectLayout: ProjectLayout,
private val projectLayout: ProjectLayout,
objectFactory: ObjectFactory
) : DefaultTask() {
@get:Classpath
Expand Down Expand Up @@ -79,6 +82,11 @@ abstract class GenerateReportsTask @Inject constructor(
@get:Input
internal abstract val ktLintVersion: Property<String>

@get:PathSensitive(PathSensitivity.RELATIVE)
@get:InputFile
@get:Optional
internal abstract val baseline: RegularFileProperty

init {
// Workaround for https://github.com/gradle/gradle/issues/2919
onlyIf {
Expand Down Expand Up @@ -107,6 +115,8 @@ abstract class GenerateReportsTask @Inject constructor(
@Suppress("UnstableApiUsage")
@TaskAction
fun generateReports() {
checkBaselineSupportedKtLintVersion()

// Classloader isolation is enough here as we just want to use some classes from KtLint classpath
// to get errors and generate files/console reports. No KtLint main object is initialized/used in this case.
val queue = workerExecutor.classLoaderIsolation { spec ->
Expand All @@ -126,6 +136,8 @@ abstract class GenerateReportsTask @Inject constructor(
param.reporterOutput.set(reporterOutput)
param.reporterOptions.set(generateReporterOptions(loadedReporter))
param.ktLintVersion.set(ktLintVersion)
param.baseline.set(baseline)
param.projectDirectory.set(projectLayout.projectDirectory)
}
}

Expand All @@ -136,6 +148,8 @@ abstract class GenerateReportsTask @Inject constructor(
param.verbose.set(verbose)
param.generatedReportsPaths.from(loadedReporters.values)
param.ktLintVersion.set(ktLintVersion)
param.baseline.set(baseline)
param.projectDirectory.set(projectLayout.projectDirectory)
}
}

Expand Down Expand Up @@ -163,6 +177,12 @@ abstract class GenerateReportsTask @Inject constructor(
return options.toMap()
}

private fun checkBaselineSupportedKtLintVersion() {
if (baseline.isPresent && SemVer.parse(ktLintVersion.get()) < SemVer(0, 41, 0)) {
throw GradleException("Baseline support is only enabled for KtLint versions 0.41.0+.")
}
}

internal enum class LintType(
val suffix: String
) {
Expand Down
Loading