Prototype detekt sonar kotlin plugin - #21

This commit is contained in:
abosch
2017-06-13 19:09:11 +02:00
committed by Artur Bosch
parent 23a040229f
commit e8bd20d8f7
14 changed files with 488 additions and 3 deletions

3
.gitignore vendored
View File

@@ -155,4 +155,5 @@ target/
*/build/
.gradletasknamecache
/detekt-cli/src/test/kotlin/io/gitlab/arturbosch/detekt/cli/ReproduceSpec.kt
/detekt-cli/src/test/kotlin/io/gitlab/arturbosch/detekt/cli/Test.kt
/detekt-cli/src/test/kotlin/io/gitlab/arturbosch/detekt/cli/Test.kt
*.iml

View File

@@ -15,6 +15,7 @@ plugins {
id "com.jfrog.bintray" version "1.7.3"
id 'com.github.ben-manes.versions' version '0.13.0'
id "com.github.johnrengelman.shadow" version "1.2.3"
id "org.sonarqube" version "2.5"
// id "com.github.hierynomus.license" version "0.13.1"
}

View File

@@ -30,12 +30,14 @@ interface HasEntity {
get() = location.source
val charPosition: TextLocation
get() = location.text
val file: String
get() = location.file
val signature: String
get() = entity.signature
val name: String
get() = entity.name
val inClass: String
get() = entity.className
val signature: String
get() = entity.signature
}
/**

View File

@@ -0,0 +1,40 @@
plugins {
id "com.iadams.sonar-packaging" version "0.1.4"
}
configurations {
compile.extendsFrom kotlinCompile
testCompile.extendsFrom kotlinTest
// testRuntime.extendsFrom junitPlatform
}
dependencies {
compile project(':detekt-core')
compile project(':detekt-formatting')
compile project(':detekt-rules')
compile "org.sonarsource.sonarqube:sonar-plugin-api:$sonarVersion"
testCompile "org.sonarsource.sonarqube:sonar-testing-harness:$sonarVersion"
testRuntime "org.junit.platform:junit-platform-launcher:$junitPlatformVersion"
testRuntime "org.junit.platform:junit-platform-console:$junitPlatformVersion"
testRuntime "org.jetbrains.spek:spek-junit-platform-engine:$spekVersion"
}
sonarPackaging {
serverUrl = 'http://localhost:9000'
pluginDir = '/tmp/sonarqube/extensions/plugins'
pluginKey = 'example'
pluginClass = 'io.gitlab.arturbosch.detekt.sonar.DetektPlugin'
pluginName = 'Sonar kotlin plugin using detekt.'
pluginDescription = 'Work in progress kotlin sonar plugin.'
pluginParent = null
pluginLicense = 'APACHE 2.0'
requirePlugins = null
pluginUrl = 'http://github.com/arturbosch/detekt'
pluginIssueTrackerUrl = 'http://github.com/arturbosch/detekt/issues'
pluginTermsConditionsUrl = 'http://github.com/arturbosch/detekt'
pluginSourceUrl = 'http://github.com/arturbosch/detekt'
pluginDevelopers = 'Artur Bosch'
skipDependenciesPackaging = false
useChildFirstClassLoader = false
basePlugin = ''
}

183
detekt-sonar-kotlin/pom.xml Normal file
View File

@@ -0,0 +1,183 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.gitlab.arturbosch.detekt</groupId>
<artifactId>detekt-sonar-kotlin</artifactId>
<packaging>sonar-plugin</packaging>
<version>0.1-SNAPSHOT</version>
<name>SonarKotlin</name>
<description>SonarQube plugin for Kotlin based on Detekt</description>
<url>https://github.com/arturbosch/detekt</url>
<licenses>
<license>
<name>Apache 2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0</url>
<distribution>repo</distribution>
</license>
</licenses>
<scm>
<connection>scm:git:git@github.com:arturbosch/detekt.git</connection>
<url>http://github.com/arturbosch/detekt</url>
<tag>HEAD</tag>
</scm>
<developers>
<developer>
<name>Artur Bosch</name>
</developer>
</developers>
<organization>
<name>Artur Bosch</name>
<url>https://arturbosch.gitlab.io</url>
</organization>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<sonar.version>5.6</sonar.version>
<jdk.min.version>1.8</jdk.min.version>
<kotlin.version>1.1.2</kotlin.version>
</properties>
<dependencies>
<dependency>
<groupId>org.sonarsource.sonarqube</groupId>
<artifactId>sonar-plugin-api</artifactId>
<version>${sonar.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.gitlab.arturbosch.detekt</groupId>
<artifactId>detekt-formatting</artifactId>
<version>1.0.0.M11</version>
</dependency>
<dependency>
<groupId>io.gitlab.arturbosch.detekt</groupId>
<artifactId>detekt-rules</artifactId>
<version>1.0.0.M11</version>
</dependency>
<!-- unit tests -->
<dependency>
<groupId>org.sonarsource.sonarqube</groupId>
<artifactId>sonar-testing-harness</artifactId>
<version>${sonar.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-test-junit</artifactId>
<version>${kotlin.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<testSourceDirectory>src/test/kotlin</testSourceDirectory>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.sonarsource.scanner.maven</groupId>
<artifactId>sonar-maven-plugin</artifactId>
<version>3.3.0.603</version>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.sonarsource.sonar-packaging-maven-plugin</groupId>
<artifactId>sonar-packaging-maven-plugin</artifactId>
<version>1.16</version>
<extensions>true</extensions>
<configuration>
<pluginClass>io.gitlab.arturbosch.detekt.sonar.DetektPlugin</pluginClass>
<pluginKey>kotlin</pluginKey>
<pluginIssueTrackerUrl>https://github.com/arturbosch/detekt/issues</pluginIssueTrackerUrl>
</configuration>
</plugin>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>testCompile</id>
<phase>test-compile</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
<configuration>
<source>${jdk.min.version}</source>
<target>${jdk.min.version}</target>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>central</id>
<name>bintray</name>
<url>http://jcenter.bintray.com</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>central</id>
<name>bintray-plugins</name>
<url>http://jcenter.bintray.com</url>
</pluginRepository>
</pluginRepositories>
</project>

View File

@@ -0,0 +1,10 @@
package io.gitlab.arturbosch.detekt.sonar
const val KOTLIN_KEY = "kotlin"
const val KOTLIN_NAME = "Kotlin"
const val KOTLIN_FILE_SUFFIX = ".kt"
const val KOTLIN_SCRIPT_SUFFIX = ".kts"
const val DETEKT_SENSOR = "DetektSensor"
const val DETEKT_REPOSITORY = "detekt-kotlin"
const val DETEKT_ANALYZER = "Detekt-based Kotlin Analyzer"

View File

@@ -0,0 +1,19 @@
package io.gitlab.arturbosch.detekt.sonar
import org.sonar.api.Plugin
/**
* @author Artur Bosch
*/
class DetektPlugin : Plugin {
override fun define(context: Plugin.Context) {
context.addExtensions(listOf(
KotlinLanguage::class.java,
KotlinProfile::class.java,
DetektSensor::class.java,
DetektRulesDefinition::class.java
))
}
}

View File

@@ -0,0 +1,18 @@
package io.gitlab.arturbosch.detekt.sonar
import org.sonar.api.server.rule.RulesDefinition
/**
* @author Artur Bosch
*/
class DetektRulesDefinition : RulesDefinition {
override fun define(context: RulesDefinition.Context) {
context.createRepository(DETEKT_REPOSITORY, KOTLIN_KEY)
.setName(DETEKT_ANALYZER)
.createRules()
.done()
}
}

View File

@@ -0,0 +1,89 @@
package io.gitlab.arturbosch.detekt.sonar
import io.gitlab.arturbosch.detekt.api.Finding
import io.gitlab.arturbosch.detekt.api.YamlConfig
import io.gitlab.arturbosch.detekt.cli.ClasspathResourceConverter
import io.gitlab.arturbosch.detekt.core.COMPLEXITY_KEY
import io.gitlab.arturbosch.detekt.core.DetektFacade
import io.gitlab.arturbosch.detekt.core.Detektion
import io.gitlab.arturbosch.detekt.core.LLOC_KEY
import io.gitlab.arturbosch.detekt.core.PathFilter
import io.gitlab.arturbosch.detekt.core.ProcessingSettings
import org.sonar.api.batch.fs.InputFile
import org.sonar.api.batch.sensor.Sensor
import org.sonar.api.batch.sensor.SensorContext
import org.sonar.api.batch.sensor.SensorDescriptor
import org.sonar.api.batch.sensor.issue.NewIssue
/**
* @author Artur Bosch
*/
class DetektSensor : Sensor {
override fun describe(descriptor: SensorDescriptor) {
descriptor.name(DETEKT_SENSOR).onlyOnLanguage(KOTLIN_KEY)
}
override fun execute(context: SensorContext) {
val fileSystem = context.fileSystem()
val baseDir = fileSystem.baseDir()
val filters = ".*/test/.*,.*/resources/.*,.*/build/.*".split(",").map { PathFilter(it) }
val config = YamlConfig.loadResource(ClasspathResourceConverter().convert("/default-detekt-config.yml"))
val settings = ProcessingSettings(baseDir.toPath(), config = config, pathFilters = filters)
val detektor = DetektFacade.instance(settings)
val detektion = detektor.run()
projectIssues(detektion, context)
projectMetrics(detektion, context)
}
private fun projectIssues(detektion: Detektion, context: SensorContext) {
val fileSystem = context.fileSystem()
val baseDir = fileSystem.baseDir()
detektion.findings.forEach { ruleSet, findings ->
println("RuleSet: $ruleSet - ${findings.size}")
findings.forEach { issue ->
println(issue.compact())
val inputFile = fileSystem.inputFile(fileSystem.predicates().`is`(baseDir.resolve(issue.location.file)))
if (inputFile != null) {
val newIssue = context.newIssue()
.forRule(findKey(issue.id))
.gap(2.0) // TODO how to setup?
.primaryLocation(issue, inputFile)
println(newIssue)
newIssue.save()
} else {
println("No file found for ${issue.location.file}")
}
}
}
}
private fun NewIssue.primaryLocation(issue: Finding, inputFile: InputFile): NewIssue {
val (line, _) = issue.startPosition
val newIssueLocation = newLocation()
.on(inputFile)
.at(inputFile.selectLine(line))
.message("What does this do?")
return this.at(newIssueLocation)
}
private fun projectMetrics(detektion: Detektion, context: SensorContext) {
detektion.getData(COMPLEXITY_KEY)?.let {
context.newMeasure<Int>()
.withValue(it)
.forMetric(MCCABE_PROJECT)
.on(context.module())
.save()
}
detektion.getData(LLOC_KEY)?.let {
context.newMeasure<Int>()
.withValue(it)
.forMetric(LLOC_PROJECT)
.on(context.module())
.save()
}
}
}

View File

@@ -0,0 +1,13 @@
package io.gitlab.arturbosch.detekt.sonar
import org.sonar.api.resources.AbstractLanguage
/**
* @author Artur Bosch
*/
class KotlinLanguage : AbstractLanguage(KOTLIN_KEY, KOTLIN_NAME) {
override fun getFileSuffixes(): Array<String>
= arrayOf(KOTLIN_FILE_SUFFIX, KOTLIN_SCRIPT_SUFFIX)
}

View File

@@ -0,0 +1,15 @@
package io.gitlab.arturbosch.detekt.sonar
import org.sonar.api.profiles.ProfileDefinition
import org.sonar.api.profiles.RulesProfile
import org.sonar.api.utils.ValidationMessages
/**
* @author Artur Bosch
*/
class KotlinProfile : ProfileDefinition() {
override fun createProfile(validation: ValidationMessages): RulesProfile
= RulesProfile.create(KOTLIN_NAME, KOTLIN_NAME)
}

View File

@@ -0,0 +1,21 @@
package io.gitlab.arturbosch.detekt.sonar
import org.sonar.api.measures.CoreMetrics
import org.sonar.api.measures.Metric
/**
* @author Artur Bosch
*/
val LLOC_PROJECT: Metric<Int> = Metric.Builder("lloc", "Logical Lines of Code", Metric.ValueType.INT)
.setDescription("Number of logical lines of code.")
.setDirection(Metric.DIRECTION_NONE)
.setQualitative(false)
.setDomain(CoreMetrics.DOMAIN_GENERAL)
.create<Int>()
val MCCABE_PROJECT: Metric<Int> = Metric.Builder("project_complexity", "Project Cyclomatic Complexity", Metric.ValueType.INT)
.setDescription("Complexity of the whole project based on McCabe.")
.setDirection(Metric.DIRECTION_NONE)
.setQualitative(false)
.setDomain(CoreMetrics.DOMAIN_GENERAL)
.create<Int>()

View File

@@ -0,0 +1,70 @@
package io.gitlab.arturbosch.detekt.sonar
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.formatting.FormattingProvider
import io.gitlab.arturbosch.detekt.rules.complexity.ComplexityProvider
import io.gitlab.arturbosch.detekt.rules.documentation.CommentSmellProvider
import io.gitlab.arturbosch.detekt.rules.providers.CodeSmellProvider
import io.gitlab.arturbosch.detekt.rules.providers.EmptyCodeProvider
import io.gitlab.arturbosch.detekt.rules.providers.ExceptionsProvider
import io.gitlab.arturbosch.detekt.rules.providers.PotentialBugProvider
import io.gitlab.arturbosch.detekt.rules.style.StyleGuideProvider
import org.sonar.api.rule.RuleKey
import org.sonar.api.rule.RuleStatus
import org.sonar.api.rule.Severity
import org.sonar.api.server.rule.RulesDefinition
private val CONFIG = Config.empty
private val COMPLEXITY_RULES = ComplexityProvider().instance(CONFIG).rules
private val STYLES_RULES = StyleGuideProvider().instance(CONFIG).rules
private val CODE_SMELL_RULES = CodeSmellProvider().instance(CONFIG).rules
private val COMMENTS_RULES = CommentSmellProvider().instance(CONFIG).rules
private val EMPTY_RULES = EmptyCodeProvider().instance(CONFIG).rules
private val EXCEPTIONS_RULES = ExceptionsProvider().instance(CONFIG).rules
private val POTENTIAL_BUGS_RULES = PotentialBugProvider().instance(CONFIG).rules
private val FORMATTING_RULES = FormattingProvider().instance(CONFIG).rules
val RULE_KEYS = COMPLEXITY_RULES.map { defineRuleKey(it.id) } +
STYLES_RULES.map { defineRuleKey(it.id) } +
CODE_SMELL_RULES.map { defineRuleKey(it.id) } +
COMMENTS_RULES.map { defineRuleKey(it.id) } +
EMPTY_RULES.map { defineRuleKey(it.id) } +
EXCEPTIONS_RULES.map { defineRuleKey(it.id) } +
POTENTIAL_BUGS_RULES.map { defineRuleKey(it.id) } +
FORMATTING_RULES.map { defineRuleKey(it.id) }
fun findKey(id: String) = RULE_KEYS.find { it.rule() == id }
private fun defineRuleKey(id: String): RuleKey = RuleKey.of(DETEKT_REPOSITORY, id)
fun RulesDefinition.NewRepository.createRules() = this.apply {
COMPLEXITY_RULES.map { defineRule(it, "20min") } +
STYLES_RULES.map { defineRule(it, "5min") } +
CODE_SMELL_RULES.map { defineRule(it, "20min") } +
COMMENTS_RULES.map { defineRule(it, "10min") } +
EXCEPTIONS_RULES.map { defineRule(it, "10min") } +
EMPTY_RULES.map { defineRule(it, "5min") } +
POTENTIAL_BUGS_RULES.map { defineRule(it, "10min") } +
FORMATTING_RULES.map { defineRule(it, "1min") }
}
private fun RulesDefinition.NewRepository.defineRule(rule: Rule, dept: String) {
val newRule = createRule(rule.id).setName(rule.id)
.setHtmlDescription("No description yet for ${rule.id}!")
.setTags(rule.severity.name.toLowerCase())
.setStatus(RuleStatus.READY)
.setSeverity(severityMap[rule.severity])
newRule.setDebtRemediationFunction(newRule.debtRemediationFunctions().linear(dept))
}
private val severityMap = mapOf(
Rule.Severity.CodeSmell to Severity.MAJOR,
Rule.Severity.Defect to Severity.CRITICAL,
Rule.Severity.Maintainability to Severity.MAJOR,
Rule.Severity.Minor to Severity.MINOR,
Rule.Severity.Security to Severity.BLOCKER,
Rule.Severity.Style to Severity.INFO,
Rule.Severity.Warning to Severity.INFO
)

View File

@@ -9,3 +9,6 @@ hamkrestVersion=1.2.3.0
yamlVersion=1.18
jcommanderVersion=1.72
assertjVersion=3.6.2
sonarVersion=5.6
systemProp.sonar.host.url=http://localhost:9000