From e8bd20d8f79bf8147e3dfe9998316085e71205ee Mon Sep 17 00:00:00 2001 From: abosch Date: Tue, 13 Jun 2017 19:09:11 +0200 Subject: [PATCH] Prototype detekt sonar kotlin plugin - #21 --- .gitignore | 3 +- build.gradle | 1 + .../gitlab/arturbosch/detekt/api/Findings.kt | 6 +- detekt-sonar-kotlin/build.gradle | 40 ++++ detekt-sonar-kotlin/pom.xml | 183 ++++++++++++++++++ .../arturbosch/detekt/sonar/Constants.kt | 10 + .../arturbosch/detekt/sonar/DetektPlugin.kt | 19 ++ .../detekt/sonar/DetektRuleDefinition.kt | 18 ++ .../arturbosch/detekt/sonar/DetektSensor.kt | 89 +++++++++ .../arturbosch/detekt/sonar/KotlinLanguage.kt | 13 ++ .../arturbosch/detekt/sonar/KotlinProfile.kt | 15 ++ .../gitlab/arturbosch/detekt/sonar/Metrics.kt | 21 ++ .../detekt/sonar/RuleDefinitions.kt | 70 +++++++ gradle.properties | 3 + 14 files changed, 488 insertions(+), 3 deletions(-) create mode 100644 detekt-sonar-kotlin/build.gradle create mode 100644 detekt-sonar-kotlin/pom.xml create mode 100644 detekt-sonar-kotlin/src/main/kotlin/io/gitlab/arturbosch/detekt/sonar/Constants.kt create mode 100644 detekt-sonar-kotlin/src/main/kotlin/io/gitlab/arturbosch/detekt/sonar/DetektPlugin.kt create mode 100644 detekt-sonar-kotlin/src/main/kotlin/io/gitlab/arturbosch/detekt/sonar/DetektRuleDefinition.kt create mode 100644 detekt-sonar-kotlin/src/main/kotlin/io/gitlab/arturbosch/detekt/sonar/DetektSensor.kt create mode 100644 detekt-sonar-kotlin/src/main/kotlin/io/gitlab/arturbosch/detekt/sonar/KotlinLanguage.kt create mode 100644 detekt-sonar-kotlin/src/main/kotlin/io/gitlab/arturbosch/detekt/sonar/KotlinProfile.kt create mode 100644 detekt-sonar-kotlin/src/main/kotlin/io/gitlab/arturbosch/detekt/sonar/Metrics.kt create mode 100644 detekt-sonar-kotlin/src/main/kotlin/io/gitlab/arturbosch/detekt/sonar/RuleDefinitions.kt 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