Skip to content

Commit

Permalink
TelemetryData at KotlinSensor level, passed to the MetricVisitor
Browse files Browse the repository at this point in the history
  • Loading branch information
antonioaversa committed Jan 23, 2025
1 parent ba412e6 commit 0ff8570
Show file tree
Hide file tree
Showing 8 changed files with 169 additions and 6 deletions.
45 changes: 45 additions & 0 deletions gradle/verification-metadata.xml
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,11 @@
<sha256 value="8540247fad9e06baefa8fb45eb313802d019f485f14300e0f9d6b556ed88e753" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.google.protobuf" name="protobuf-java" version="4.29.0">
<artifact name="protobuf-java-4.29.0.jar">
<sha256 value="16901851ebe5e89fe88aaad3c26866373695bc2e30627bb8932847e2f5fc2e76" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.googlecode.concurrent-trees" name="concurrent-trees" version="2.6.1">
<artifact name="concurrent-trees-2.6.1.jar">
<sha256 value="04e3724984e2a5cbf55606cfa372a5bd3d3c5d2a21533a7004e3cde539761fa5" origin="Verified"/>
Expand Down Expand Up @@ -410,6 +415,11 @@
<sha256 value="f700de80ac270d0344fdea7468201d8b9c805e5c648331c3619f2ee067ccfc59" origin="Verified"/>
</artifact>
</component>
<component group="commons-codec" name="commons-codec" version="1.17.1">
<artifact name="commons-codec-1.17.1.jar">
<sha256 value="f9f6cb103f2ddc3c99a9d80ada2ae7bf0685111fd6bffccb72033d1da4e6ff23" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="commons-io" name="commons-io" version="2.11.0">
<artifact name="commons-io-2.11.0.jar">
<sha256 value="961b2f6d87dbacc5d54abf45ab7a6e2495f89b75598962d8c723cea9bc210908" origin="Verified"/>
Expand All @@ -420,6 +430,11 @@
<sha256 value="f41f7baacd716896447ace9758621f62c1c6b0a91d89acee488da26fc477c84f" origin="Verified"/>
</artifact>
</component>
<component group="commons-io" name="commons-io" version="2.18.0">
<artifact name="commons-io-2.18.0.jar">
<sha256 value="f3ca0f8d63c40e23a56d54101c60d5edee136b42d84bfb85bc7963093109cf8b" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="commons-io" name="commons-io" version="2.7">
<artifact name="commons-io-2.7.jar">
<sha256 value="4547858fff38bbf15262d520685b184a3dce96897bc1844871f055b96e8f6e95" origin="Verified"/>
Expand Down Expand Up @@ -640,6 +655,11 @@
<sha256 value="2bacc065deb36a20476e0b61caa7c97444e4ef667d4c48cc28e0c7c959f85c68" origin="Verified"/>
</artifact>
</component>
<component group="jakarta.annotation" name="jakarta.annotation-api" version="3.0.0">
<artifact name="jakarta.annotation-api-3.0.0.jar">
<sha256 value="b01f55552284cfb149411e64eabca75e942d26d2e1786b32914250e4330afaa2" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="javax.annotation" name="javax.annotation-api" version="1.3.2">
<artifact name="javax.annotation-api-1.3.2.jar">
<sha256 value="e04ba5195bcd555dc95650f7cc614d151e4bcd52d29a10b8aa2197f3ab89ab9b" origin="Verified"/>
Expand Down Expand Up @@ -680,6 +700,11 @@
<sha256 value="b697fe3f94cfc4f7e2a87bddf78d15cd10d8c86cbe56ae9196a62d6edbf6b76d" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.commons" name="commons-csv" version="1.12.0">
<artifact name="commons-csv-1.12.0.jar">
<sha256 value="e11a33d65e5f2e92769c0b13c548e0c2da01e37c1581417ea3fd7d5d9474fee6" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.commons" name="commons-csv" version="1.9.0">
<artifact name="commons-csv-1.9.0.jar">
<sha256 value="c418d6aab4db4f1f70983d355de8d7c1e755c754820a92294da2e5f5081022cc" origin="Verified"/>
Expand All @@ -695,6 +720,11 @@
<sha256 value="7b96bf3ee68949abb5bc465559ac270e0551596fa34523fddf890ec418dde13c" origin="Verified"/>
</artifact>
</component>
<component group="org.apache.commons" name="commons-lang3" version="3.17.0">
<artifact name="commons-lang3-3.17.0.jar">
<sha256 value="6ee731df5c8e5a2976a1ca023b6bb320ea8d3539fbe64c8a1d5cb765127c33b4" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.commons" name="commons-lang3" version="3.9">
<artifact name="commons-lang3-3.9.jar">
<sha256 value="de2e1dcdcf3ef917a8ce858661a06726a9a944f28e33ad7f9e08bea44dc3c230" origin="Verified"/>
Expand Down Expand Up @@ -1805,6 +1835,11 @@
<sha256 value="0d77abf571e335b0928592cc6184a695976ba3c351ec79f08c0094678533dbdb" origin="Verified"/>
</artifact>
</component>
<component group="org.sonarsource.api.plugin" name="sonar-plugin-api" version="11.0.0.2664">
<artifact name="sonar-plugin-api-11.0.0.2664.jar">
<sha256 value="ce2f63cea5a0c0b984c6f6726bd5fc5c0adbd530f2f3071ce22415a5f3a7f801" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.sonarsource.api.plugin" name="sonar-plugin-api" version="9.14.0.375">
<artifact name="sonar-plugin-api-9.14.0.375.jar">
<sha256 value="ca679099e12bb6a797b60ad35954f4d0cb0b87b136a96f74245745118256e3df" origin="Verified"/>
Expand Down Expand Up @@ -1855,6 +1890,11 @@
<sha256 value="2e39b1d6b14f9f1087a6fa7d89e22dd30b6e3438b1f47f49f5453e92fb78849f" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.sonarsource.sonarqube" name="sonar-plugin-api-impl" version="25.1.0.102122">
<artifact name="sonar-plugin-api-impl-25.1.0.102122.jar">
<sha256 value="a80ccd0acae6781c00e6c276e964ee253f6b0745972de14f86503dfa4bbcd541" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.sonarsource.sonarqube" name="sonar-ws" version="10.0.0.68432">
<artifact name="sonar-ws-10.0.0.68432.jar">
<sha256 value="8b90140326e73ed114d804ad50b97fa936d32d1bbea9b93037f6c21ed6443ffa" origin="Verified"/>
Expand All @@ -1865,6 +1905,11 @@
<sha256 value="392eaf15f6cce7ab9f8f5cd42bb884ace80c37cead396d49f431216ff45b8314" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.sonarsource.sonarqube" name="sonar-ws" version="25.1.0.102122">
<artifact name="sonar-ws-25.1.0.102122.jar">
<sha256 value="5d213ae59e07cd7b3788c1ba9743afed75613435d8d8aa4ea952eb6569bfa5cd" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.springframework" name="spring-aop" version="4.3.26.RELEASE">
<artifact name="spring-aop-4.3.26.RELEASE.jar">
<sha256 value="bd12ca116d9094efbd98ae1539b18014c7e48cf3ca2efc2022295ac1aeb5c15b" origin="Verified"/>
Expand Down
2 changes: 1 addition & 1 deletion settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ dependencyResolutionManagement {
val mockk = version("mockk", "1.13.3")
val orchestrator = version("orchestrator", "5.0.0.2065")
val sonarlint = version("sonarlint", "9.5.0.76302")
val sonarqube = version("sonarqube", "10.7.0.96327") // TODO: to be checked
val sonarqube = version("sonarqube", "25.1.0.102122")

library("assertj-core", "org.assertj", "assertj-core").versionRef(assertj)
library("junit-jupiter", "org.junit.jupiter", "junit-jupiter").versionRef(junit)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* SonarSource Kotlin
* Copyright (C) 2018-2025 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
package org.sonarsource.kotlin.api.telemetry

import org.sonar.api.batch.sensor.SensorContext
import org.sonar.api.utils.Version

private object VersionConstants {
val MIN_SQS_SUPPORTED: Version = Version.create(10, 9)
}

class TelemetryData {
var hasAndroidImports = false
var hasAndroidImportsReportedAsTrue = false

fun report(sensorContext: SensorContext) {
if (hasAndroidImportsReportedAsTrue) return
val isTelemetrySupported = sensorContext.runtime().apiVersion.isGreaterThanOrEqual(VersionConstants.MIN_SQS_SUPPORTED)
if (!isTelemetrySupported) return
sensorContext.addTelemetryProperty("kotlin.android", if (hasAndroidImports) "1" else "0")
hasAndroidImportsReportedAsTrue = hasAndroidImports
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* SonarSource Kotlin
* Copyright (C) 2018-2025 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
package org.sonarsource.kotlin.api.telemetry

import io.mockk.every
import io.mockk.spyk
import org.junit.jupiter.api.Test

import org.junit.jupiter.api.Assertions.*
import org.sonar.api.batch.sensor.SensorContext
import org.sonar.api.batch.sensor.internal.SensorContextTester
import java.nio.file.Path

class TelemetryDataTest {
@Test
fun hasAndroidImportsReportedAsTrue() {
val data = TelemetryData()
assertFalse(data.hasAndroidImports)
assertFalse(data.hasAndroidImportsReportedAsTrue)
data.hasAndroidImports = false
assertFalse(data.hasAndroidImportsReportedAsTrue)
val sensorContext = SensorContextTester.create(Path.of("."))
data.report(sensorContext)
assertFalse(data.hasAndroidImportsReportedAsTrue)
data.hasAndroidImports = true
data.report(sensorContext)
assertTrue(data.hasAndroidImportsReportedAsTrue)
data.hasAndroidImports = false
data.report(sensorContext)
assertTrue(data.hasAndroidImportsReportedAsTrue)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,15 @@ import org.sonarsource.kotlin.api.checks.InputFileContext
import org.sonarsource.kotlin.api.checks.getContent
import org.sonarsource.kotlin.api.checks.linesOfCode
import org.sonarsource.kotlin.api.frontend.KotlinFileContext
import org.sonarsource.kotlin.api.telemetry.TelemetryData
import org.sonarsource.kotlin.api.visiting.KotlinFileVisitor

const val NOSONAR_PREFIX = "NOSONAR"

class MetricVisitor(
private val fileLinesContextFactory: FileLinesContextFactory,
private val noSonarFilter: NoSonarFilter,
private val telemetryData: TelemetryData, // Some metrics are stored in telemetry
) : KotlinFileVisitor() {
private lateinit var ktMetricVisitor: KtMetricVisitor

Expand All @@ -68,6 +70,8 @@ class MetricVisitor(
ktMetricVisitor.executableLines.forEach { line -> fileLinesContext.setIntValue(CoreMetrics.EXECUTABLE_LINES_DATA_KEY, line, 1) }
fileLinesContext.save()
noSonarFilter.noSonarInFile(ctx.inputFile, ktMetricVisitor.nosonarLines)

telemetryData.hasAndroidImports = telemetryData.hasAndroidImports || ktMetricVisitor.hasAndroidImports
}

fun commentLines() = ktMetricVisitor.commentLines.toSet()
Expand All @@ -88,10 +92,12 @@ class MetricVisitor(
}

private class KtMetricVisitor : KtTreeVisitorVoid() {
companion object {
private companion object {
val ANDROID_PACKAGES = setOf(Name.identifier("android"), Name.identifier("androidx"))
}

// User metrics

var linesOfCode: Set<Int> = mutableSetOf()
private set

Expand Down Expand Up @@ -119,7 +125,9 @@ private class KtMetricVisitor : KtTreeVisitorVoid() {
var cognitiveComplexity = 0
private set

var hasAndroidImports = false // Metric stored in telemetry
// Telemetry metrics

var hasAndroidImports = false
private set

override fun visitKtFile(file: KtFile) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package org.sonarsource.kotlin.metrics

import com.intellij.openapi.util.Disposer
import io.mockk.every
import org.assertj.core.api.Assertions.assertThat
import org.jetbrains.kotlin.config.LanguageVersion
import org.junit.jupiter.api.AfterEach
Expand All @@ -37,6 +38,7 @@ import org.sonar.api.measures.FileLinesContext
import org.sonar.api.measures.FileLinesContextFactory
import org.sonarsource.kotlin.api.checks.InputFileContextImpl
import org.sonarsource.kotlin.api.frontend.Environment
import org.sonarsource.kotlin.api.telemetry.TelemetryData
import org.sonarsource.kotlin.testapi.kotlinTreeOf
import java.nio.charset.StandardCharsets
import java.nio.file.Path
Expand Down Expand Up @@ -76,7 +78,7 @@ internal class MetricVisitorTest {
)
)
).thenReturn(mockFileLinesContext)
visitor = MetricVisitor(mockFileLinesContextFactory, mockNoSonarFilter)
visitor = MetricVisitor(mockFileLinesContextFactory, mockNoSonarFilter, TelemetryData())
}

@Test
Expand Down Expand Up @@ -366,6 +368,7 @@ internal class MetricVisitorTest {
""".trimIndent())
assert(false, "import mylibrary.android.MyClass")
assert(false, "import androidy.core.view.WindowCompat")
assert(false, "import")
assert(false, "package android")
assert(false, "class android {}")
assert(false, "fun android() = 42")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import org.sonarsource.kotlin.api.frontend.analyzeAndGetBindingContext
import org.sonarsource.kotlin.api.logging.trace
import org.sonarsource.kotlin.api.sensors.AbstractKotlinSensor
import org.sonarsource.kotlin.api.sensors.AbstractKotlinSensorExecuteContext
import org.sonarsource.kotlin.api.telemetry.TelemetryData
import org.sonarsource.kotlin.plugin.caching.ContentHashCache
import org.sonarsource.kotlin.plugin.cpd.CopyPasteDetector
import org.sonarsource.kotlin.plugin.cpd.copyCPDTokensFromPrevious
Expand All @@ -58,12 +59,23 @@ class KotlinSensor(
): AbstractKotlinSensor(
checkFactory, language, KOTLIN_CHECKS
) {
val telemetryData = TelemetryData()

override fun describe(descriptor: SensorDescriptor) {
descriptor
.onlyOnLanguage(language.key)
.name(language.name + " Sensor")
}

override fun execute(sensorContext: SensorContext) {
super.execute(sensorContext)
// The MetricsVisitor instantiated by the visitors method keeps a shared reference
// to the TelemetryData of this sensor, and updates it accordingly. The report method
// of TelemetryData takes care of not sending metrics more than once, when execute is
// run multiple times.
telemetryData.report(sensorContext)
}

override fun getExecuteContext(
sensorContext: SensorContext,
filesToAnalyze: Iterable<InputFile>,
Expand Down Expand Up @@ -93,13 +105,13 @@ class KotlinSensor(
if (sensorContext.runtime().product == SonarProduct.SONARLINT) {
listOf(
IssueSuppressionVisitor(),
MetricVisitor(fileLinesContextFactory, noSonarFilter),
MetricVisitor(fileLinesContextFactory, noSonarFilter, telemetryData),
KtChecksVisitor(checks),
)
} else {
listOf(
IssueSuppressionVisitor(),
MetricVisitor(fileLinesContextFactory, noSonarFilter),
MetricVisitor(fileLinesContextFactory, noSonarFilter, telemetryData),
KtChecksVisitor(checks),
CopyPasteDetector(),
SyntaxHighlighter(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,18 @@ internal class KotlinSensorTest : AbstractSensorTest() {
assertAnalysisIsNotIncremental(files)
}

@Test
fun `execute reports telemetry`() {
val sensor = sensor(checkFactory())
var telemetrySet = false
val context = spyk(context) {
every { addTelemetryProperty(any(), any()) } answers { telemetrySet = true }
}
assertThat(telemetrySet).isFalse()
sensor.execute(context)
assertThat(telemetrySet).isTrue()
}

private fun assertAnalysisIsIncremental(files: Map<InputFile.Status, InputFile>) {
val addedFile = files[InputFile.Status.ADDED]
val changedFile = files[InputFile.Status.CHANGED]
Expand Down

0 comments on commit 0ff8570

Please sign in to comment.