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 db5c66e
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* 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

class TelemetryData {
var hasAndroidImports = false
var hasAndroidImportsReportedAsTrue = false

fun report(sensorContext: SensorContext) {
if (!hasAndroidImportsReportedAsTrue) {
sensorContext.addTelemetryProperty("kotlin.android", if (hasAndroidImports) "1" else "0")
hasAndroidImportsReportedAsTrue = hasAndroidImports
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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.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 = MockSensorContext(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)
}
}

// Mock to SensorContextTester delegation needed as addTelemetryProperty raises
// UnsupportedOperationException and SensorContextTester has private constructors
private class MockSensorContext(wrapped: SensorContextTester) : SensorContext by wrapped {
override fun addTelemetryProperty(key: String, value: String) { }
}
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 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 db5c66e

Please sign in to comment.