Skip to content
This repository was archived by the owner on Jan 6, 2024. It is now read-only.

Use detekt's tooling api #125

Merged
merged 1 commit into from
Aug 10, 2020
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
21 changes: 9 additions & 12 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>io.github.detekt</groupId>
<artifactId>sonar-detekt</artifactId>
<version>2.1.0</version>
<version>2.2.0</version>
<url>https://github.com/detekt/sonar-kotlin</url>

<licenses>
Expand All @@ -20,8 +20,8 @@

<properties>
<kotlin.version>1.3.72</kotlin.version>
<spek.version>2.0.10</spek.version>
<detekt.version>1.9.1</detekt.version>
<spek.version>2.0.12</spek.version>
<detekt.version>1.11.0-RC2</detekt.version>
<sonar.api.version>7.9.3</sonar.api.version>
<assertj.version>3.16.1</assertj.version>
<jcommander.version>1.78</jcommander.version>
Expand Down Expand Up @@ -65,7 +65,7 @@
</dependency>
<dependency>
<groupId>io.gitlab.arturbosch.detekt</groupId>
<artifactId>detekt-cli</artifactId>
<artifactId>detekt-tooling</artifactId>
<version>${detekt.version}</version>
</dependency>
<dependency>
Expand All @@ -87,18 +87,12 @@
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-compiler-embeddable</artifactId>
<version>${kotlin.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>com.beust</groupId>
<artifactId>jcommander</artifactId>
<version>${jcommander.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-test-junit</artifactId>
Expand Down Expand Up @@ -139,7 +133,7 @@
<plugin>
<groupId>org.sonarsource.sonar-packaging-maven-plugin</groupId>
<artifactId>sonar-packaging-maven-plugin</artifactId>
<version>1.18.0.372</version>
<version>1.19.0.397</version>
<extensions>true</extensions>
<configuration>
<pluginKey>detekt</pluginKey>
Expand All @@ -164,6 +158,9 @@
<goal>compile</goal>
</goals>
<configuration>
<args>
<arg>-Xopt-in=kotlin.RequiresOptIn</arg>
</args>
<sourceDirs>
<sourceDir>${project.basedir}/src/main/kotlin</sourceDir>
</sourceDirs>
Expand Down Expand Up @@ -235,7 +232,7 @@
<plugin>
<groupId>com.github.ozsie</groupId>
<artifactId>detekt-maven-plugin</artifactId>
<version>1.9.1</version>
<version>1.10.0</version>
<executions>
<execution>
<phase>verify</phase>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,43 +1,47 @@
package io.gitlab.arturbosch.detekt.sonar.rules

import io.github.detekt.tooling.api.DefaultConfigurationProvider
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.MultiRule
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.RuleSetProvider
import io.gitlab.arturbosch.detekt.api.internal.BaseRule
import io.gitlab.arturbosch.detekt.cli.loadDefaultConfig
import io.gitlab.arturbosch.detekt.sonar.foundation.REPOSITORY_KEY
import org.sonar.api.rule.RuleKey
import java.util.ServiceLoader

val defaultConfig: Config = loadDefaultConfig()
internal val defaultConfig: Config = DefaultConfigurationProvider.load().get()

val excludedDuplicates = setOf(
"Filename", // from KtLint; same as MatchingDeclarationName
"MaximumLineLength", // from KtLint; same as MaxLineLength
"NoUnitReturn", // from KtLint; same as OptionalUnit
"NoWildcardImports" // from KtLint; same as WildcardImport
/**
* Exclude similar or duplicated rule implementations from other rule sets than the default one.
*/
internal val excludedDuplicates = setOf(
"Filename", // MatchingDeclarationName
"MaximumLineLength", // MaxLineLength
"NoUnitReturn", // OptionalUnit
"NoWildcardImports" // WildcardImport
)

val allLoadedRules: List<Rule> = ServiceLoader.load(RuleSetProvider::class.java, Config::class.java.classLoader)
.asSequence()
.flatMap { loadRules(it).asSequence() }
.flatMap { (it as? MultiRule)?.rules?.asSequence() ?: sequenceOf(it) }
.filterIsInstance<Rule>()
.filterNot { it.ruleId in excludedDuplicates }
.toList()
internal val allLoadedRules: List<Rule> =
ServiceLoader.load(RuleSetProvider::class.java, Config::class.java.classLoader)
.asSequence()
.flatMap { loadRules(it).asSequence() }
.flatMap { (it as? MultiRule)?.rules?.asSequence() ?: sequenceOf(it) }
.filterIsInstance<Rule>()
.filterNot { it.ruleId in excludedDuplicates }
.toList()

private fun loadRules(provider: RuleSetProvider): List<BaseRule> {
val subConfig = defaultConfig.subConfig(provider.ruleSetId)
return provider.instance(subConfig).rules
}

val ruleKeys: List<DetektRuleKey> = allLoadedRules.map { defineRuleKey(it) }
internal val ruleKeys: List<DetektRuleKey> = allLoadedRules.map { defineRuleKey(it) }

val ruleKeyLookup: Map<String, DetektRuleKey> = ruleKeys.associateBy { it.ruleKey }
internal val ruleKeyLookup: Map<String, DetektRuleKey> = ruleKeys.associateBy { it.ruleKey }

data class DetektRuleKey(
internal data class DetektRuleKey(
private val repositoryKey: String,
val ruleKey: String,
val active: Boolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class DetektRulesDefinition : RulesDefinition {
}
}

fun RulesDefinition.NewRepository.createRules(): RulesDefinition.NewRepository =
private fun RulesDefinition.NewRepository.createRules(): RulesDefinition.NewRepository =
apply { allLoadedRules.map { defineRule(it) } }

private fun RulesDefinition.NewRepository.defineRule(rule: Rule) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package io.gitlab.arturbosch.detekt.sonar.rules

import io.gitlab.arturbosch.detekt.api.Severity

val severityTranslations: Map<Severity, String> = mapOf(
internal val severityTranslations: Map<Severity, String> = mapOf(
Severity.CodeSmell to org.sonar.api.rule.Severity.MAJOR,
Severity.Defect to org.sonar.api.rule.Severity.CRITICAL,
Severity.Maintainability to org.sonar.api.rule.Severity.MAJOR,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,62 +1,99 @@
package io.gitlab.arturbosch.detekt.sonar.sensor

import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.cli.CliArgs
import io.gitlab.arturbosch.detekt.cli.createFilters
import io.gitlab.arturbosch.detekt.cli.loadConfiguration
import io.gitlab.arturbosch.detekt.core.ProcessingSettings
import io.github.detekt.tooling.api.spec.ProcessingSpec
import io.github.detekt.tooling.api.spec.RulesSpec
import io.gitlab.arturbosch.detekt.sonar.foundation.BASELINE_KEY
import io.gitlab.arturbosch.detekt.sonar.foundation.CONFIG_PATH_KEY
import io.gitlab.arturbosch.detekt.sonar.foundation.PATH_FILTERS_DEFAULTS
import io.gitlab.arturbosch.detekt.sonar.foundation.PATH_FILTERS_KEY
import io.gitlab.arturbosch.detekt.sonar.foundation.logger
import org.sonar.api.batch.sensor.SensorContext
import org.sonar.api.config.Configuration
import java.io.File
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths

fun createProcessingSettings(context: SensorContext): ProcessingSettings {
val baseDir = context.fileSystem().baseDir()
internal fun createSpec(context: SensorContext): ProcessingSpec {
val baseDir = context.fileSystem().baseDir().toPath()
val settings = context.config()
val filters = CliArgs {
excludes = settings.get(PATH_FILTERS_KEY).orElse(PATH_FILTERS_DEFAULTS)
}.createFilters()
val config = chooseConfig(baseDir, settings)
return ProcessingSettings(
inputPaths = listOf(baseDir.toPath()),
config = config,
pathFilters = filters,
outPrinter = System.out,
errPrinter = System.err
)
return createSpec(baseDir, settings)
}

internal fun chooseConfig(baseDir: File, configuration: Configuration): Config {
val externalConfigPath = tryFindDetektConfigurationFile(configuration, baseDir)
internal fun createSpec(baseDir: Path, configuration: Configuration): ProcessingSpec {
val configPath = tryFindDetektConfigurationFile(baseDir, configuration)
val baselineFile = tryFindBaselineFile(baseDir, configuration)

val possibleParseArguments = CliArgs().apply {
config = externalConfigPath?.path
failFast = true // always use FailFast config to activate all detekt rules
autoCorrect = false // never change user files and conflict with sonar's reporting
return ProcessingSpec {
project {
basePath = baseDir
inputPaths = listOf(baseDir)
excludes = getProjectExcludeFilters(configuration)
}
rules {
activateExperimentalRules = true // publish all; quality profiles will filter
maxIssuePolicy = RulesSpec.MaxIssuePolicy.AllowAny
autoCorrect = false // never change user files and conflict with sonar's reporting
}
config {
useDefaultConfig = true
configPaths = listOfNotNull(configPath)
}
baseline {
path = baselineFile
}
}
}

internal fun getProjectExcludeFilters(configuration: Configuration): List<String> =
configuration.get(PATH_FILTERS_KEY)
.orElse(PATH_FILTERS_DEFAULTS)
.splitToSequence(",", ";")
.map { it.trim() }
.filter { it.isNotEmpty() }
.toList()

internal fun tryFindBaselineFile(baseDir: Path, config: Configuration): Path? {
fun createBaselineFacade(path: Path): Path? {
logger.info("Registered baseline path: $path")
var baselinePath = path

if (Files.notExists(baselinePath)) {
baselinePath = baseDir.resolve(path)
}

return possibleParseArguments.loadConfiguration()
if (Files.notExists(baselinePath)) {
val parentFile = baseDir.parent
if (parentFile != null) {
baselinePath = parentFile.resolve(path)
} else {
return null
}
}
return baselinePath
}
return config.get(BASELINE_KEY)
.map { Paths.get(it) }
.map(::createBaselineFacade)
.orElse(null)
}

private fun tryFindDetektConfigurationFile(configuration: Configuration, baseDir: File): File? {
return configuration.get(CONFIG_PATH_KEY).map { path ->
private val supportedYamlEndings = setOf(".yaml", ".yml")

internal fun tryFindDetektConfigurationFile(baseDir: Path, configuration: Configuration): Path? =
configuration.get(CONFIG_PATH_KEY).map { path ->
logger.info("Registered config path: $path")
var configFile = File(path)
var configFile = Paths.get(path)

if (!configFile.exists() || configFile.endsWith(".yaml")) {
configFile = File(baseDir.path, path)
if (Files.notExists(configFile) || supportedYamlEndings.any { path.toString().endsWith(it) }) {
configFile = baseDir.resolve(path)
}
if (!configFile.exists() || configFile.endsWith(".yaml")) {
val parentFile = baseDir.parentFile
if (Files.notExists(configFile) || supportedYamlEndings.any { path.toString().endsWith(it) }) {
val parentFile = baseDir.parent
if (parentFile != null) {
configFile = File(parentFile.path, path)
configFile = parentFile.resolve(path)
} else {
return@map null
}
}
configFile
}.orElse(null)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package io.gitlab.arturbosch.detekt.sonar.sensor

import io.gitlab.arturbosch.detekt.core.DetektFacade
import io.github.detekt.tooling.api.AnalysisResult
import io.github.detekt.tooling.api.DetektProvider
import io.github.detekt.tooling.api.UnexpectedError
import io.gitlab.arturbosch.detekt.api.UnstableApi
import io.gitlab.arturbosch.detekt.sonar.foundation.DETEKT_SENSOR
import io.gitlab.arturbosch.detekt.sonar.foundation.LANGUAGE_KEY
import org.sonar.api.batch.sensor.Sensor
Expand All @@ -13,10 +16,20 @@ class DetektSensor : Sensor {
descriptor.name(DETEKT_SENSOR).onlyOnLanguage(LANGUAGE_KEY)
}

@OptIn(UnstableApi::class)
override fun execute(context: SensorContext) {
val settings = createProcessingSettings(context)
val facade = DetektFacade.create(settings)
val spec = createSpec(context)
val facade = DetektProvider.load().get(spec)
val result = facade.run()
IssueReporter(result, context).run()
checkErrors(result)
IssueReporter(checkNotNull(result.container), context).run()
}

private fun checkErrors(result: AnalysisResult) {
when (val error = result.error) {
is UnexpectedError -> throw error.cause
null -> return
else -> throw error
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,12 @@ package io.gitlab.arturbosch.detekt.sonar.sensor

import io.gitlab.arturbosch.detekt.api.Detektion
import io.gitlab.arturbosch.detekt.api.Finding
import io.gitlab.arturbosch.detekt.cli.baseline.BaselineFacade
import io.gitlab.arturbosch.detekt.sonar.foundation.BASELINE_KEY
import io.gitlab.arturbosch.detekt.sonar.foundation.logger
import io.gitlab.arturbosch.detekt.sonar.rules.excludedDuplicates
import io.gitlab.arturbosch.detekt.sonar.rules.ruleKeyLookup
import org.sonar.api.batch.fs.InputFile
import org.sonar.api.batch.sensor.SensorContext
import org.sonar.api.batch.sensor.issue.NewIssue
import org.sonar.api.config.Configuration
import java.io.File

class IssueReporter(
private val result: Detektion,
Expand All @@ -20,41 +16,14 @@ class IssueReporter(

private val fileSystem = context.fileSystem()
private val baseDir = fileSystem.baseDir()
private val config = context.config()

fun run() {
val baseline = tryFindBaseline(config, baseDir)
for ((ruleSet, findings) in result.findings) {
logger.info("RuleSet: $ruleSet - ${findings.size}")
val filtered = baseline?.filter(findings) ?: findings
filtered.forEach(this::reportIssue)
findings.forEach(this::reportIssue)
}
}

private fun tryFindBaseline(config: Configuration, baseDir: File): BaselineFacade? {
fun createBaselineFacade(path: String): BaselineFacade? {
logger.info("Registered baseline path: $path")
var baselinePath = File(path)

if (!baselinePath.exists()) {
baselinePath = File(baseDir.path, path)
}

if (!baselinePath.exists()) {
val parentFile = baseDir.parentFile
if (parentFile != null) {
baselinePath = File(parentFile.path, path)
} else {
return null
}
}
return BaselineFacade(baselinePath.toPath())
}
return config.get(BASELINE_KEY)
.map(::createBaselineFacade)
.orElse(null)
}

private fun reportIssue(issue: Finding) {
if (issue.id in excludedDuplicates) {
return
Expand Down
Loading