diff --git a/.gitignore b/.gitignore
index 4691a9f49..50b59ebae 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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
\ No newline at end of file
+/detekt-cli/src/test/kotlin/io/gitlab/arturbosch/detekt/cli/Test.kt
+*.iml
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 0ab1e6fcc..ca629e548 100644
--- a/build.gradle
+++ b/build.gradle
@@ -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"
}
diff --git a/detekt-api/src/main/kotlin/io/gitlab/arturbosch/detekt/api/Findings.kt b/detekt-api/src/main/kotlin/io/gitlab/arturbosch/detekt/api/Findings.kt
index ce8f39eb5..03b48588f 100644
--- a/detekt-api/src/main/kotlin/io/gitlab/arturbosch/detekt/api/Findings.kt
+++ b/detekt-api/src/main/kotlin/io/gitlab/arturbosch/detekt/api/Findings.kt
@@ -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
}
/**
diff --git a/detekt-sonar-kotlin/build.gradle b/detekt-sonar-kotlin/build.gradle
new file mode 100644
index 000000000..b8b752550
--- /dev/null
+++ b/detekt-sonar-kotlin/build.gradle
@@ -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 = ''
+}
\ No newline at end of file
diff --git a/detekt-sonar-kotlin/pom.xml b/detekt-sonar-kotlin/pom.xml
new file mode 100644
index 000000000..b8a9b6103
--- /dev/null
+++ b/detekt-sonar-kotlin/pom.xml
@@ -0,0 +1,183 @@
+
+
+ 4.0.0
+
+ io.gitlab.arturbosch.detekt
+ detekt-sonar-kotlin
+ sonar-plugin
+ 0.1-SNAPSHOT
+
+ SonarKotlin
+ SonarQube plugin for Kotlin based on Detekt
+ https://github.com/arturbosch/detekt
+
+
+
+ Apache 2.0
+ https://www.apache.org/licenses/LICENSE-2.0
+ repo
+
+
+
+
+ scm:git:git@github.com:arturbosch/detekt.git
+ http://github.com/arturbosch/detekt
+ HEAD
+
+
+
+
+ Artur Bosch
+
+
+
+ Artur Bosch
+ https://arturbosch.gitlab.io
+
+
+
+ UTF-8
+ 5.6
+ 1.8
+ 1.1.2
+
+
+
+
+ org.sonarsource.sonarqube
+ sonar-plugin-api
+ ${sonar.version}
+ provided
+
+
+ io.gitlab.arturbosch.detekt
+ detekt-formatting
+ 1.0.0.M11
+
+
+ io.gitlab.arturbosch.detekt
+ detekt-rules
+ 1.0.0.M11
+
+
+
+
+
+ org.sonarsource.sonarqube
+ sonar-testing-harness
+ ${sonar.version}
+ test
+
+
+ junit
+ junit
+ 4.12
+ test
+
+
+ org.jetbrains.kotlin
+ kotlin-test-junit
+ ${kotlin.version}
+ test
+
+
+
+
+ src/main/kotlin
+ src/test/kotlin
+
+
+
+
+ org.sonarsource.scanner.maven
+ sonar-maven-plugin
+ 3.3.0.603
+
+
+
+
+
+
+ org.sonarsource.sonar-packaging-maven-plugin
+ sonar-packaging-maven-plugin
+ 1.16
+ true
+
+ io.gitlab.arturbosch.detekt.sonar.DetektPlugin
+ kotlin
+ https://github.com/arturbosch/detekt/issues
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+ ${kotlin.version}
+
+
+ compile
+ compile
+
+ compile
+
+
+
+ test-compile
+ test-compile
+
+ test-compile
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.5.1
+
+
+ compile
+ compile
+
+ compile
+
+
+
+ testCompile
+ test-compile
+
+ testCompile
+
+
+
+
+ ${jdk.min.version}
+ ${jdk.min.version}
+
+
+
+
+
+
+
+
+ false
+
+ central
+ bintray
+ http://jcenter.bintray.com
+
+
+
+
+
+ false
+
+ central
+ bintray-plugins
+ http://jcenter.bintray.com
+
+
+
+
diff --git a/detekt-sonar-kotlin/src/main/kotlin/io/gitlab/arturbosch/detekt/sonar/Constants.kt b/detekt-sonar-kotlin/src/main/kotlin/io/gitlab/arturbosch/detekt/sonar/Constants.kt
new file mode 100644
index 000000000..19b6a331a
--- /dev/null
+++ b/detekt-sonar-kotlin/src/main/kotlin/io/gitlab/arturbosch/detekt/sonar/Constants.kt
@@ -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"
\ No newline at end of file
diff --git a/detekt-sonar-kotlin/src/main/kotlin/io/gitlab/arturbosch/detekt/sonar/DetektPlugin.kt b/detekt-sonar-kotlin/src/main/kotlin/io/gitlab/arturbosch/detekt/sonar/DetektPlugin.kt
new file mode 100644
index 000000000..2c0f8bc9c
--- /dev/null
+++ b/detekt-sonar-kotlin/src/main/kotlin/io/gitlab/arturbosch/detekt/sonar/DetektPlugin.kt
@@ -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
+ ))
+ }
+
+}
\ No newline at end of file
diff --git a/detekt-sonar-kotlin/src/main/kotlin/io/gitlab/arturbosch/detekt/sonar/DetektRuleDefinition.kt b/detekt-sonar-kotlin/src/main/kotlin/io/gitlab/arturbosch/detekt/sonar/DetektRuleDefinition.kt
new file mode 100644
index 000000000..25bbc00e1
--- /dev/null
+++ b/detekt-sonar-kotlin/src/main/kotlin/io/gitlab/arturbosch/detekt/sonar/DetektRuleDefinition.kt
@@ -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()
+ }
+
+}
\ No newline at end of file
diff --git a/detekt-sonar-kotlin/src/main/kotlin/io/gitlab/arturbosch/detekt/sonar/DetektSensor.kt b/detekt-sonar-kotlin/src/main/kotlin/io/gitlab/arturbosch/detekt/sonar/DetektSensor.kt
new file mode 100644
index 000000000..faa38413e
--- /dev/null
+++ b/detekt-sonar-kotlin/src/main/kotlin/io/gitlab/arturbosch/detekt/sonar/DetektSensor.kt
@@ -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()
+ .withValue(it)
+ .forMetric(MCCABE_PROJECT)
+ .on(context.module())
+ .save()
+ }
+ detektion.getData(LLOC_KEY)?.let {
+ context.newMeasure()
+ .withValue(it)
+ .forMetric(LLOC_PROJECT)
+ .on(context.module())
+ .save()
+ }
+ }
+}
diff --git a/detekt-sonar-kotlin/src/main/kotlin/io/gitlab/arturbosch/detekt/sonar/KotlinLanguage.kt b/detekt-sonar-kotlin/src/main/kotlin/io/gitlab/arturbosch/detekt/sonar/KotlinLanguage.kt
new file mode 100644
index 000000000..3d0bc6827
--- /dev/null
+++ b/detekt-sonar-kotlin/src/main/kotlin/io/gitlab/arturbosch/detekt/sonar/KotlinLanguage.kt
@@ -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
+ = arrayOf(KOTLIN_FILE_SUFFIX, KOTLIN_SCRIPT_SUFFIX)
+
+}
\ No newline at end of file
diff --git a/detekt-sonar-kotlin/src/main/kotlin/io/gitlab/arturbosch/detekt/sonar/KotlinProfile.kt b/detekt-sonar-kotlin/src/main/kotlin/io/gitlab/arturbosch/detekt/sonar/KotlinProfile.kt
new file mode 100644
index 000000000..0c6deb2e7
--- /dev/null
+++ b/detekt-sonar-kotlin/src/main/kotlin/io/gitlab/arturbosch/detekt/sonar/KotlinProfile.kt
@@ -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)
+
+}
\ No newline at end of file
diff --git a/detekt-sonar-kotlin/src/main/kotlin/io/gitlab/arturbosch/detekt/sonar/Metrics.kt b/detekt-sonar-kotlin/src/main/kotlin/io/gitlab/arturbosch/detekt/sonar/Metrics.kt
new file mode 100644
index 000000000..761912fe2
--- /dev/null
+++ b/detekt-sonar-kotlin/src/main/kotlin/io/gitlab/arturbosch/detekt/sonar/Metrics.kt
@@ -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 = 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()
+
+val MCCABE_PROJECT: Metric = 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()
\ No newline at end of file
diff --git a/detekt-sonar-kotlin/src/main/kotlin/io/gitlab/arturbosch/detekt/sonar/RuleDefinitions.kt b/detekt-sonar-kotlin/src/main/kotlin/io/gitlab/arturbosch/detekt/sonar/RuleDefinitions.kt
new file mode 100644
index 000000000..8cab7e016
--- /dev/null
+++ b/detekt-sonar-kotlin/src/main/kotlin/io/gitlab/arturbosch/detekt/sonar/RuleDefinitions.kt
@@ -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
+)
diff --git a/gradle.properties b/gradle.properties
index ee398c579..df55be187 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -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
\ No newline at end of file