Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add detekt compiler plugin to main project (#5492)
* Add DetektKotlinCompilerPlugin to main project * Allow both Gradle plugins to be used in the same project * Restore flag on extension to allow enabling or disabling compiler plugin * Enable detekt compiler plugin on Kotlin tasks by default This only has an effect when io.github.detekt.gradle.compiler-plugin is applied to the project. It has no effect on io.gitlab.arturbosch.detekt. * Use workaround to check for expected compiler plugin version * Copy plugin-build source into main project detekt/detekt-compiler-plugin@7f3594d * Integrate detekt-compiler-plugin into build * Fix style issues * Explain purpose of DEFAULT_COMPILER_PLUGIN_ENABLED * Use kotlinVersion-detektVersion scheme for compiler plugin versioning * Reuse constants where possible * Remove unused `detektPluginVersion` Co-authored-by: Chao Zhang <chao.zhang@instacart.com>
- Loading branch information
1 parent
8aa1c82
commit 0abd43d
Showing
24 changed files
with
913 additions
and
2 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
import de.undercouch.gradle.tasks.download.Download | ||
import de.undercouch.gradle.tasks.download.Verify | ||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile | ||
import java.io.ByteArrayOutputStream | ||
|
||
val kotlinVersion: String = libs.versions.kotlin.get() | ||
val detektVersion: String = Versions.DETEKT | ||
|
||
val kotlinCompilerChecksum: String by project | ||
|
||
group = "io.github.detekt" | ||
version = "$kotlinVersion-$detektVersion" | ||
|
||
val detektPublication = "DetektPublication" | ||
|
||
plugins { | ||
id("module") | ||
alias(libs.plugins.gradleVersions) | ||
alias(libs.plugins.shadow) | ||
alias(libs.plugins.download) | ||
} | ||
|
||
repositories { | ||
mavenCentral() | ||
mavenLocal() | ||
} | ||
|
||
dependencies { | ||
compileOnly(kotlin("stdlib")) | ||
compileOnly(kotlin("compiler-embeddable")) | ||
|
||
implementation(projects.detektApi) | ||
implementation(projects.detektTooling) | ||
runtimeOnly(projects.detektCore) | ||
runtimeOnly(projects.detektRules) | ||
|
||
testImplementation(libs.assertj) | ||
testImplementation(libs.kotlinCompileTesting) | ||
} | ||
|
||
val javaComponent = components["java"] as AdhocComponentWithVariants | ||
javaComponent.withVariantsFromConfiguration(configurations["shadowRuntimeElements"]) { | ||
skip() | ||
} | ||
|
||
tasks.shadowJar.configure { | ||
relocate("org.jetbrains.kotlin.com.intellij", "com.intellij") | ||
mergeServiceFiles() | ||
dependencies { | ||
include(dependency("io.gitlab.arturbosch.detekt:.*")) | ||
include(dependency("io.github.detekt:.*")) | ||
include(dependency("org.yaml:snakeyaml")) | ||
include(dependency("io.github.davidburstrom.contester:contester-breakpoint")) | ||
} | ||
} | ||
|
||
val verifyKotlinCompilerDownload by tasks.registering(Verify::class) { | ||
src(file("$rootDir/build/kotlinc/kotlin-compiler-$kotlinVersion.zip")) | ||
algorithm("SHA-256") | ||
checksum(kotlinCompilerChecksum) | ||
outputs.upToDateWhen { true } | ||
} | ||
|
||
val downloadKotlinCompiler by tasks.registering(Download::class) { | ||
src("https://github.com/JetBrains/kotlin/releases/download/v$kotlinVersion/kotlin-compiler-$kotlinVersion.zip") | ||
dest(file("$rootDir/build/kotlinc/kotlin-compiler-$kotlinVersion.zip")) | ||
overwrite(false) | ||
finalizedBy(verifyKotlinCompilerDownload) | ||
} | ||
|
||
val unzipKotlinCompiler by tasks.registering(Copy::class) { | ||
dependsOn(downloadKotlinCompiler) | ||
from(zipTree(downloadKotlinCompiler.get().dest)) | ||
into(file("$rootDir/build/kotlinc/$kotlinVersion")) | ||
} | ||
|
||
val testPluginKotlinc by tasks.registering(RunTestExecutable::class) { | ||
dependsOn(unzipKotlinCompiler, tasks.shadowJar) | ||
|
||
args( | ||
listOf( | ||
"$rootDir/src/test/resources/hello.kt", | ||
"-Xplugin=${tasks.shadowJar.get().archiveFile.get().asFile.absolutePath}", | ||
"-P", | ||
) | ||
) | ||
|
||
val baseExecutablePath = "${unzipKotlinCompiler.get().destinationDir}/kotlinc/bin/kotlinc" | ||
val pluginParameters = "plugin:detekt-compiler-plugin:debug=true" | ||
|
||
if (org.apache.tools.ant.taskdefs.condition.Os.isFamily("windows")) { | ||
executable(file("$baseExecutablePath.bat")) | ||
args("\"$pluginParameters\"") | ||
} else { | ||
executable(file(baseExecutablePath)) | ||
args(pluginParameters) | ||
} | ||
|
||
errorOutput = ByteArrayOutputStream() | ||
// dummy path - required for RunTestExecutable task but doesn't do anything | ||
outputDir = file("$buildDir/tmp/kotlinc") | ||
|
||
doLast { | ||
if (!errorOutput.toString().contains("warning: magicNumber:")) { | ||
throw GradleException( | ||
"kotlinc $kotlinVersion run with compiler plugin did not find MagicNumber issue as expected" | ||
) | ||
} | ||
(this as RunTestExecutable).executionResult.get().assertNormalExitValue() | ||
} | ||
} | ||
|
||
tasks.withType<KotlinCompile>().configureEach { | ||
kotlinOptions.freeCompilerArgs = listOf( | ||
"-opt-in=kotlin.RequiresOptIn" | ||
) | ||
} |
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,6 @@ | ||
kotlinCompilerChecksum=8412b31b808755f0c0d336dbb8c8443fa239bf32ddb3cdb81b305b25f0ad279e | ||
|
||
kotlin.code.style=official | ||
systemProp.sonar.host.url=http://localhost:9000 | ||
systemProp.detektVersion=detektVersion | ||
systemProp.file.encoding=UTF-8 |
42 changes: 42 additions & 0 deletions
42
...mpiler-plugin/src/main/kotlin/io/github/detekt/compiler/plugin/DetektAnalysisExtension.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,42 @@ | ||
package io.github.detekt.compiler.plugin | ||
|
||
import io.github.detekt.compiler.plugin.internal.DetektService | ||
import io.github.detekt.compiler.plugin.internal.info | ||
import io.github.detekt.tooling.api.spec.ProcessingSpec | ||
import org.jetbrains.kotlin.analyzer.AnalysisResult | ||
import org.jetbrains.kotlin.cli.common.messages.MessageCollector | ||
import org.jetbrains.kotlin.com.intellij.openapi.project.Project | ||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor | ||
import org.jetbrains.kotlin.psi.KtFile | ||
import org.jetbrains.kotlin.resolve.BindingTrace | ||
import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension | ||
import java.nio.file.FileSystems | ||
import java.nio.file.Path | ||
import java.nio.file.Paths | ||
|
||
class DetektAnalysisExtension( | ||
private val log: MessageCollector, | ||
private val spec: ProcessingSpec, | ||
private val rootPath: Path, | ||
private val excludes: Collection<String> | ||
) : AnalysisHandlerExtension { | ||
|
||
override fun analysisCompleted( | ||
project: Project, | ||
module: ModuleDescriptor, | ||
bindingTrace: BindingTrace, | ||
files: Collection<KtFile> | ||
): AnalysisResult? { | ||
if (spec.loggingSpec.debug) { | ||
log.info("$spec") | ||
} | ||
val matchers = excludes.map { FileSystems.getDefault().getPathMatcher("glob:$it") } | ||
val (includedFiles, excludedFiles) = files.partition { file -> | ||
matchers.none { it.matches(rootPath.relativize(Paths.get(file.virtualFilePath))) } | ||
} | ||
log.info("Running detekt on module '${module.name.asString()}'") | ||
excludedFiles.forEach { log.info("File excluded by filter: ${it.virtualFilePath}") } | ||
DetektService(log, spec).analyze(includedFiles, bindingTrace.bindingContext) | ||
return null | ||
} | ||
} |
132 changes: 132 additions & 0 deletions
132
...ler-plugin/src/main/kotlin/io/github/detekt/compiler/plugin/DetektCommandLineProcessor.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,132 @@ | ||
package io.github.detekt.compiler.plugin | ||
|
||
import org.jetbrains.kotlin.compiler.plugin.AbstractCliOption | ||
import org.jetbrains.kotlin.compiler.plugin.CliOption | ||
import org.jetbrains.kotlin.compiler.plugin.CliOptionProcessingException | ||
import org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor | ||
import org.jetbrains.kotlin.config.CompilerConfiguration | ||
import java.io.ByteArrayInputStream | ||
import java.io.ObjectInputStream | ||
import java.nio.file.Paths | ||
import java.util.Base64 | ||
|
||
class DetektCommandLineProcessor : CommandLineProcessor { | ||
|
||
override val pluginId: String = "detekt-compiler-plugin" | ||
|
||
@Suppress("StringLiteralDuplication") | ||
override val pluginOptions: Collection<AbstractCliOption> = listOf( | ||
CliOption( | ||
Options.config, | ||
"<path|paths>", | ||
"Comma separated paths to detekt config files.", | ||
false | ||
), | ||
CliOption( | ||
Options.configDigest, | ||
"<digest>", | ||
"A digest calculated from the content of the config files. Used for Gradle incremental task invalidation.", | ||
false | ||
), | ||
CliOption( | ||
Options.baseline, | ||
"<path>", | ||
"Path to a detekt baseline file.", | ||
false | ||
), | ||
CliOption( | ||
Options.debug, | ||
"<true|false>", | ||
"Print debug messages.", | ||
false | ||
), | ||
CliOption( | ||
Options.isEnabled, | ||
"<true|false>", | ||
"Should detekt run?", | ||
false | ||
), | ||
CliOption( | ||
Options.useDefaultConfig, | ||
"<true|false>", | ||
"Use the default detekt config as baseline.", | ||
false | ||
), | ||
CliOption( | ||
Options.allRules, | ||
"<true|false>", | ||
"Turns on all the rules.", | ||
false | ||
), | ||
CliOption( | ||
Options.disableDefaultRuleSets, | ||
"<true|false>", | ||
"Disables all default detekt rulesets and will only run detekt with custom rules " + | ||
"defined in plugins passed in with `detektPlugins` configuration.", | ||
false | ||
), | ||
CliOption( | ||
Options.parallel, | ||
"<true|false>", | ||
"Enables parallel compilation and analysis of source files.", | ||
false | ||
), | ||
CliOption( | ||
Options.rootPath, | ||
"<path>", | ||
"Root path used to relativize paths when using exclude patterns.", | ||
false | ||
), | ||
CliOption( | ||
Options.excludes, | ||
"<base64-encoded globs>", | ||
"A base64-encoded list of the globs used to exclude paths from scanning.", | ||
false | ||
), | ||
CliOption( | ||
Options.report, | ||
"<report-id:path>", | ||
"Generates a report for given 'report-id' and stores it on given 'path'. " + | ||
"Available 'report-id' values: 'txt', 'xml', 'html'.", | ||
false, | ||
allowMultipleOccurrences = true | ||
) | ||
) | ||
|
||
override fun processOption(option: AbstractCliOption, value: String, configuration: CompilerConfiguration) { | ||
when (option.optionName) { | ||
Options.baseline -> configuration.put(Keys.BASELINE, Paths.get(value)) | ||
Options.config -> configuration.put(Keys.CONFIG, value.split(",;").map { Paths.get(it) }) | ||
Options.configDigest -> configuration.put(Keys.CONFIG_DIGEST, value) | ||
Options.debug -> configuration.put(Keys.DEBUG, value.toBoolean()) | ||
Options.isEnabled -> configuration.put(Keys.IS_ENABLED, value.toBoolean()) | ||
Options.useDefaultConfig -> configuration.put(Keys.USE_DEFAULT_CONFIG, value.toBoolean()) | ||
Options.allRules -> configuration.put(Keys.ALL_RULES, value.toBoolean()) | ||
Options.disableDefaultRuleSets -> configuration.put(Keys.DISABLE_DEFAULT_RULE_SETS, value.toBoolean()) | ||
Options.parallel -> configuration.put(Keys.PARALLEL, value.toBoolean()) | ||
Options.rootPath -> configuration.put(Keys.ROOT_PATH, Paths.get(value)) | ||
Options.excludes -> configuration.put(Keys.EXCLUDES, value.decodeToGlobSet()) | ||
Options.report -> configuration.put( | ||
Keys.REPORTS, | ||
value.substringBefore(':'), | ||
Paths.get(value.substringAfter(':')), | ||
) | ||
else -> throw CliOptionProcessingException("Unknown option: ${option.optionName}") | ||
} | ||
} | ||
} | ||
|
||
private fun String.decodeToGlobSet(): List<String> { | ||
val b = Base64.getDecoder().decode(this) | ||
val bi = ByteArrayInputStream(b) | ||
|
||
return ObjectInputStream(bi).use { inputStream -> | ||
val globs = mutableListOf<String>() | ||
|
||
repeat(inputStream.readInt()) { | ||
globs.add(inputStream.readUTF()) | ||
} | ||
|
||
globs | ||
} | ||
} |
34 changes: 34 additions & 0 deletions
34
...piler-plugin/src/main/kotlin/io/github/detekt/compiler/plugin/DetektComponentRegistrar.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,34 @@ | ||
package io.github.detekt.compiler.plugin | ||
|
||
import io.github.detekt.compiler.plugin.internal.toSpec | ||
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys | ||
import org.jetbrains.kotlin.cli.common.messages.MessageCollector | ||
import org.jetbrains.kotlin.com.intellij.mock.MockProject | ||
import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar | ||
import org.jetbrains.kotlin.config.CompilerConfiguration | ||
import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension | ||
import java.nio.file.Paths | ||
|
||
class DetektComponentRegistrar : ComponentRegistrar { | ||
|
||
override fun registerProjectComponents( | ||
project: MockProject, | ||
configuration: CompilerConfiguration | ||
) { | ||
if (configuration.get(Keys.IS_ENABLED) == false) { | ||
return | ||
} | ||
|
||
val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE) | ||
|
||
AnalysisHandlerExtension.registerExtension( | ||
project, | ||
DetektAnalysisExtension( | ||
messageCollector, | ||
configuration.toSpec(messageCollector), | ||
configuration.get(Keys.ROOT_PATH, Paths.get(System.getProperty("user.dir"))), | ||
configuration.getList(Keys.EXCLUDES) | ||
) | ||
) | ||
} | ||
} |
37 changes: 37 additions & 0 deletions
37
detekt-compiler-plugin/src/main/kotlin/io/github/detekt/compiler/plugin/Keys.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,37 @@ | ||
package io.github.detekt.compiler.plugin | ||
|
||
import org.jetbrains.kotlin.config.CompilerConfigurationKey | ||
import java.nio.file.Path | ||
|
||
object Options { | ||
|
||
@Suppress("NonBooleanPropertyPrefixedWithIs") | ||
const val isEnabled: String = "isEnabled" | ||
const val debug: String = "debug" | ||
const val config = "config" | ||
const val configDigest: String = "configDigest" | ||
const val baseline: String = "baseline" | ||
const val useDefaultConfig: String = "useDefaultConfig" | ||
const val allRules: String = "allRules" | ||
const val disableDefaultRuleSets: String = "disableDefaultRuleSets" | ||
const val parallel: String = "parallel" | ||
const val rootPath = "rootDir" | ||
const val excludes = "excludes" | ||
const val report = "report" | ||
} | ||
|
||
object Keys { | ||
|
||
val DEBUG = CompilerConfigurationKey.create<Boolean>(Options.debug) | ||
val IS_ENABLED = CompilerConfigurationKey.create<Boolean>(Options.isEnabled) | ||
val CONFIG = CompilerConfigurationKey.create<List<Path>>(Options.config) | ||
val CONFIG_DIGEST = CompilerConfigurationKey.create<String>(Options.configDigest) | ||
val BASELINE = CompilerConfigurationKey.create<Path>(Options.baseline) | ||
val USE_DEFAULT_CONFIG = CompilerConfigurationKey.create<Boolean>(Options.useDefaultConfig) | ||
val ALL_RULES = CompilerConfigurationKey.create<Boolean>(Options.allRules) | ||
val DISABLE_DEFAULT_RULE_SETS = CompilerConfigurationKey.create<Boolean>(Options.disableDefaultRuleSets) | ||
val PARALLEL = CompilerConfigurationKey.create<Boolean>(Options.parallel) | ||
val ROOT_PATH = CompilerConfigurationKey.create<Path>(Options.rootPath) | ||
val EXCLUDES = CompilerConfigurationKey.create<List<String>>(Options.excludes) | ||
val REPORTS = CompilerConfigurationKey.create<Map<String, Path>>(Options.report) | ||
} |
11 changes: 11 additions & 0 deletions
11
...ler-plugin/src/main/kotlin/io/github/detekt/compiler/plugin/internal/AppendableAdapter.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 io.github.detekt.compiler.plugin.internal | ||
|
||
internal class AppendableAdapter(val logging: (String) -> Unit) : Appendable { | ||
|
||
override fun append(csq: CharSequence): Appendable = also { logging(csq.toString()) } | ||
|
||
override fun append(csq: CharSequence, start: Int, end: Int): Appendable = | ||
also { logging(csq.substring(start, end)) } | ||
|
||
override fun append(c: Char): Appendable = also { logging(c.toString()) } | ||
} |
Oops, something went wrong.