From d647c392827befe6cf0c519478127412f88a9d45 Mon Sep 17 00:00:00 2001 From: Nikita Ogorodnikov Date: Fri, 9 Oct 2026 16:43:04 +0200 Subject: [PATCH 1/4] RUM-18937: Split Kotlin version for the buildscript classpath and for the runtime classpath --- LICENSE-3rdparty.csv | 2 +- .../com/datadog/gradle/config/KotlinConfig.kt | 8 ++- .../plugin/apisurface/ApiSurfacePlugin.kt | 2 +- .../plugin/config/DatadogBuildExtension.kt | 7 +- dd-sdk-android-core/build.gradle.kts | 4 +- dd-sdk-android-internal/build.gradle.kts | 4 +- .../build.gradle.kts | 2 +- .../dd-sdk-android-flags/build.gradle.kts | 2 +- features/dd-sdk-android-logs/build.gradle.kts | 2 +- features/dd-sdk-android-ndk/build.gradle.kts | 2 +- .../dd-sdk-android-profiling/build.gradle.kts | 4 +- .../build.gradle.kts | 2 +- .../build.gradle.kts | 2 +- .../build.gradle.kts | 2 +- features/dd-sdk-android-rum/build.gradle.kts | 4 +- .../build.gradle.kts | 2 +- .../build.gradle.kts | 2 +- .../build.gradle.kts | 2 +- .../dd-sdk-android-trace-api/build.gradle.kts | 2 +- .../build.gradle.kts | 2 +- .../build.gradle.kts | 2 +- .../dd-sdk-android-trace/build.gradle.kts | 4 +- .../dd-sdk-android-webview/build.gradle.kts | 2 +- gradle/libs.versions.toml | 40 +++++------ instrumented/integration/build.gradle.kts | 2 +- .../dd-sdk-android-apollo/build.gradle.kts | 2 +- .../dd-sdk-android-coil/build.gradle.kts | 2 +- .../dd-sdk-android-coil3/build.gradle.kts | 2 +- .../dd-sdk-android-compose/build.gradle.kts | 2 +- .../dd-sdk-android-cronet/build.gradle.kts | 2 +- .../dd-sdk-android-fresco/build.gradle.kts | 2 +- .../dd-sdk-android-glide/build.gradle.kts | 2 +- .../build.gradle.kts | 2 +- .../dd-sdk-android-okhttp/build.gradle.kts | 2 +- .../build.gradle.kts | 2 +- .../dd-sdk-android-rx/build.gradle.kts | 2 +- .../build.gradle.kts | 2 +- .../dd-sdk-android-timber/build.gradle.kts | 2 +- .../build.gradle.kts | 2 +- .../dd-sdk-android-tv/build.gradle.kts | 2 +- reliability/core-it/build.gradle.kts | 2 +- reliability/single-fit/logs/build.gradle.kts | 2 +- .../single-fit/okhttp/build.gradle.kts | 2 +- .../single-fit/profiling/build.gradle.kts | 2 +- reliability/single-fit/rum/build.gradle.kts | 2 +- reliability/single-fit/trace/build.gradle.kts | 2 +- reliability/stub-core/build.gradle.kts | 2 +- reliability/stub-feature/build.gradle.kts | 2 +- reliability/with-backend/build.gradle.kts | 2 +- sample/automotive/build.gradle.kts | 2 +- sample/benchmark/build.gradle.kts | 2 +- sample/kotlin/build.gradle.kts | 2 +- sample/tv/build.gradle.kts | 2 +- sample/vendor-lib/build.gradle.kts | 2 +- tools/benchmark/build.gradle.kts | 2 +- tools/detekt/build.gradle.kts | 4 +- tools/javabackport/build.gradle.kts | 2 +- tools/lint/build.gradle.kts | 3 +- tools/noopfactory/build.gradle.kts | 9 ++- .../noopfactory/NoOpFactoryProviderTest.kt | 72 ++++++++++++------- .../test/resources/gen/NoOpEnumInterface.kt | 2 +- .../src/test/resources/src/EnumInterface.kt | 2 +- tools/unit/build.gradle.kts | 3 +- 63 files changed, 147 insertions(+), 119 deletions(-) diff --git a/LICENSE-3rdparty.csv b/LICENSE-3rdparty.csv index 3f06293d21..df8c3cc962 100644 --- a/LICENSE-3rdparty.csv +++ b/LICENSE-3rdparty.csv @@ -140,6 +140,7 @@ import(test),com.jakewharton.android.repackaged,Apache-2.0,"Copyright (C) 2007 T import(test),com.parse.bolts,"BSD License","Copyright (c) Facebook, Inc. and its affiliates." import(test),com.squareup,Apache-2.0,"Copyright 2015 Square, Inc." import(test),dev.drewhamilton.poko,Apache-2.0,Copyright 2020-Present Drew Hamilton +import(test),dev.zacsweers.kctfork,MPL-2.0,"Copyright (C) 2023 Thilo Schuchort" import(test),greatest,ICT,"Copyright (c) 2011-2018 Scott Vokes " import(test),io.github.davidburstrom.contester,Apache-2.0,"Copyright 2022-2024 David Burström" import(test),io.github.detekt.sarif4k,Apache-2.0,"Copyright 2021 Artur Bosch & Contributors" @@ -185,7 +186,6 @@ build,com.fasterxml.jackson.dataformat,Apache-2.,"Copyright (c) 2007- Tatu Salor build,com.fasterxml.jackson.module,Apache-2.0,"Copyright (c) 2007- Tatu Saloranta" build,com.fasterxml.woodstox,Apache-2.0,"Copyright (c) 2007- Tatu Saloranta" build,com.github.ajalt.clikt,Apache-2.0,"Copyright clikt authors" -build,com.github.tschuchortdev,MPL-2.0,"Copyright (C) 2023 Thilo Schuchort" build,com.google.android,Apache-2.0,Copyright (C) 2013 The Android Open Source Project build,com.google.api.grpc,Apache-2.0,Copyright 2020 Google LLC build,com.google.auto,Apache-2.0,"Copyright 2014 Google LLC" diff --git a/build-logic/src/main/kotlin/com/datadog/gradle/config/KotlinConfig.kt b/build-logic/src/main/kotlin/com/datadog/gradle/config/KotlinConfig.kt index ae0ec65c4b..a4b5aa3b6c 100644 --- a/build-logic/src/main/kotlin/com/datadog/gradle/config/KotlinConfig.kt +++ b/build-logic/src/main/kotlin/com/datadog/gradle/config/KotlinConfig.kt @@ -13,15 +13,17 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile internal fun Project.kotlinConfig( evaluateWarningsAsErrors: Boolean = true, - jvmBytecodeTarget: JvmTarget = JvmTarget.JVM_17 + jvmBytecodeTarget: JvmTarget = JvmTarget.JVM_17, + kotlinApiVersion: KotlinVersion = KotlinVersion.KOTLIN_2_0, + kotlinLanguageVersion: KotlinVersion = KotlinVersion.KOTLIN_2_0 ) { taskConfig { compilerOptions { jvmTarget.set(jvmBytecodeTarget) val isCI = System.getenv("CI").toBoolean() allWarningsAsErrors.set(evaluateWarningsAsErrors && isCI) - apiVersion.set(KotlinVersion.KOTLIN_2_0) - languageVersion.set(KotlinVersion.KOTLIN_2_0) + apiVersion.set(kotlinApiVersion) + languageVersion.set(kotlinLanguageVersion) } } } diff --git a/build-logic/src/main/kotlin/com/datadog/gradle/plugin/apisurface/ApiSurfacePlugin.kt b/build-logic/src/main/kotlin/com/datadog/gradle/plugin/apisurface/ApiSurfacePlugin.kt index df9aca1eb8..049776fa39 100644 --- a/build-logic/src/main/kotlin/com/datadog/gradle/plugin/apisurface/ApiSurfacePlugin.kt +++ b/build-logic/src/main/kotlin/com/datadog/gradle/plugin/apisurface/ApiSurfacePlugin.kt @@ -101,7 +101,7 @@ class ApiSurfacePlugin : Plugin { private fun Project.createParserConfiguration(): Configuration { val kotlinVersion = extensions.getByType() .named("libs") - .findVersion("kotlin") + .findVersion("kotlinBuildscript") .orElseThrow { IllegalStateException("No `kotlin` version found in the version catalog") } return configurations.create(CONFIGURATION_PARSER) { isCanBeConsumed = false diff --git a/build-logic/src/main/kotlin/com/datadog/gradle/plugin/config/DatadogBuildExtension.kt b/build-logic/src/main/kotlin/com/datadog/gradle/plugin/config/DatadogBuildExtension.kt index c4bf681856..72919c652d 100644 --- a/build-logic/src/main/kotlin/com/datadog/gradle/plugin/config/DatadogBuildExtension.kt +++ b/build-logic/src/main/kotlin/com/datadog/gradle/plugin/config/DatadogBuildExtension.kt @@ -13,6 +13,7 @@ import com.datadog.gradle.config.kotlinConfig import com.datadog.gradle.config.publishingConfig import org.gradle.api.Project import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.dsl.KotlinVersion import javax.inject.Inject /** @@ -29,9 +30,11 @@ abstract class DatadogBuildExtension @Inject constructor( /** Aligns Kotlin compilation: bytecode target, API/language version and warning strictness. */ fun applyKotlinConfig( evaluateWarningsAsErrors: Boolean = true, - jvmBytecodeTarget: JvmTarget = JvmTarget.JVM_17 + jvmBytecodeTarget: JvmTarget = JvmTarget.JVM_17, + kotlinApiVersion: KotlinVersion = KotlinVersion.KOTLIN_2_0, + kotlinLanguageVersion: KotlinVersion = KotlinVersion.KOTLIN_2_0 ) { - project.kotlinConfig(evaluateWarningsAsErrors, jvmBytecodeTarget) + project.kotlinConfig(evaluateWarningsAsErrors, jvmBytecodeTarget, kotlinApiVersion, kotlinLanguageVersion) } /** Applies the shared `com.android.library` setup: SDK levels, source sets, lint and packaging. */ diff --git a/dd-sdk-android-core/build.gradle.kts b/dd-sdk-android-core/build.gradle.kts index 4d73db6ea8..9d4695df18 100644 --- a/dd-sdk-android-core/build.gradle.kts +++ b/dd-sdk-android-core/build.gradle.kts @@ -111,7 +111,7 @@ android { } dependencies { - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) // Network implementation(libs.okHttp) @@ -144,7 +144,7 @@ dependencies { unmock(libs.robolectric) // Test Fixtures - testFixturesImplementation(libs.kotlin) + testFixturesImplementation(libs.kotlinRuntime) testFixturesImplementation(libs.bundles.jUnit5) testFixturesImplementation(libs.okHttp) testFixturesImplementation(libs.bundles.testTools) diff --git a/dd-sdk-android-internal/build.gradle.kts b/dd-sdk-android-internal/build.gradle.kts index 7376c2c54d..5e6b36ff87 100644 --- a/dd-sdk-android-internal/build.gradle.kts +++ b/dd-sdk-android-internal/build.gradle.kts @@ -52,7 +52,7 @@ android { } dependencies { - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) implementation(libs.androidXAnnotation) // Generate NoOp implementations @@ -65,7 +65,7 @@ dependencies { ) } } - testFixturesImplementation(libs.kotlin) + testFixturesImplementation(libs.kotlinRuntime) testFixturesImplementation(libs.bundles.jUnit5) testFixturesImplementation(libs.bundles.testTools) testFixturesImplementation(project(":tools:unit")) { diff --git a/features/dd-sdk-android-flags-openfeature/build.gradle.kts b/features/dd-sdk-android-flags-openfeature/build.gradle.kts index 790545be70..097e24e971 100644 --- a/features/dd-sdk-android-flags-openfeature/build.gradle.kts +++ b/features/dd-sdk-android-flags-openfeature/build.gradle.kts @@ -52,7 +52,7 @@ dependencies { // OpenFeature SDK api(libs.openFeatureKotlinSdk) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) implementation(libs.coroutinesCore) implementation(libs.androidXAnnotation) diff --git a/features/dd-sdk-android-flags/build.gradle.kts b/features/dd-sdk-android-flags/build.gradle.kts index 3a52437f0c..61cf30c5dc 100644 --- a/features/dd-sdk-android-flags/build.gradle.kts +++ b/features/dd-sdk-android-flags/build.gradle.kts @@ -56,7 +56,7 @@ dependencies { implementation(project(":dd-sdk-android-internal")) implementation(libs.gson) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) implementation(libs.okHttp) implementation(libs.androidXAnnotation) implementation(libs.androidXCollection) diff --git a/features/dd-sdk-android-logs/build.gradle.kts b/features/dd-sdk-android-logs/build.gradle.kts index 2afc62f73d..22e566d3d5 100644 --- a/features/dd-sdk-android-logs/build.gradle.kts +++ b/features/dd-sdk-android-logs/build.gradle.kts @@ -54,7 +54,7 @@ android { dependencies { api(project(":dd-sdk-android-core")) implementation(project(":dd-sdk-android-internal")) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) implementation(libs.gson) implementation(libs.androidXAnnotation) diff --git a/features/dd-sdk-android-ndk/build.gradle.kts b/features/dd-sdk-android-ndk/build.gradle.kts index 17ad9c5097..c57dee5c21 100644 --- a/features/dd-sdk-android-ndk/build.gradle.kts +++ b/features/dd-sdk-android-ndk/build.gradle.kts @@ -70,7 +70,7 @@ android { dependencies { implementation(project(":dd-sdk-android-internal")) api(project(":dd-sdk-android-core")) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) implementation(libs.okHttp) testImplementation(project(":tools:unit")) { diff --git a/features/dd-sdk-android-profiling/build.gradle.kts b/features/dd-sdk-android-profiling/build.gradle.kts index 93da54b5f6..d063abe7e8 100644 --- a/features/dd-sdk-android-profiling/build.gradle.kts +++ b/features/dd-sdk-android-profiling/build.gradle.kts @@ -55,7 +55,7 @@ android { dependencies { api(project(":dd-sdk-android-core")) implementation(project(":dd-sdk-android-internal")) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) implementation(libs.androidXCore) implementation(libs.androidXCoreKtx) implementation(libs.gson) @@ -78,7 +78,7 @@ dependencies { unmock(libs.robolectric) // Test Fixtures - testFixturesImplementation(libs.kotlin) + testFixturesImplementation(libs.kotlinRuntime) testFixturesImplementation(project(":dd-sdk-android-internal")) testFixturesImplementation(libs.androidXAnnotation) } diff --git a/features/dd-sdk-android-rum-debug-widget/build.gradle.kts b/features/dd-sdk-android-rum-debug-widget/build.gradle.kts index b9d017d0c8..63510f246b 100644 --- a/features/dd-sdk-android-rum-debug-widget/build.gradle.kts +++ b/features/dd-sdk-android-rum-debug-widget/build.gradle.kts @@ -46,7 +46,7 @@ android { dependencies { implementation(project(":dd-sdk-android-internal")) implementation(project(":features:dd-sdk-android-rum")) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) // Android Instrumentation implementation(libs.androidXFragment) diff --git a/features/dd-sdk-android-rum-internal/build.gradle.kts b/features/dd-sdk-android-rum-internal/build.gradle.kts index 22c5c19101..acfdf26b7b 100644 --- a/features/dd-sdk-android-rum-internal/build.gradle.kts +++ b/features/dd-sdk-android-rum-internal/build.gradle.kts @@ -44,7 +44,7 @@ android { dependencies { implementation(project(":dd-sdk-android-core")) implementation(project(":dd-sdk-android-internal")) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) testImplementation(project(":tools:unit")) { attributes { diff --git a/features/dd-sdk-android-rum-prelaunch/build.gradle.kts b/features/dd-sdk-android-rum-prelaunch/build.gradle.kts index ee7ecfa0d6..902d10c2c5 100644 --- a/features/dd-sdk-android-rum-prelaunch/build.gradle.kts +++ b/features/dd-sdk-android-rum-prelaunch/build.gradle.kts @@ -43,7 +43,7 @@ android { dependencies { implementation(project(":features:dd-sdk-android-rum-internal")) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) testImplementation(project(":tools:unit")) { attributes { diff --git a/features/dd-sdk-android-rum/build.gradle.kts b/features/dd-sdk-android-rum/build.gradle.kts index b8c7a2646c..14fa3a062d 100644 --- a/features/dd-sdk-android-rum/build.gradle.kts +++ b/features/dd-sdk-android-rum/build.gradle.kts @@ -64,7 +64,7 @@ dependencies { api(project(":dd-sdk-android-core")) implementation(project(":dd-sdk-android-internal")) implementation(project(":features:dd-sdk-android-rum-internal")) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) implementation(libs.gson) implementation(libs.okHttp) @@ -105,7 +105,7 @@ dependencies { ) } } - testFixturesImplementation(libs.kotlin) + testFixturesImplementation(libs.kotlinRuntime) testFixturesImplementation(libs.bundles.jUnit5) testFixturesImplementation(libs.okHttp) testFixturesImplementation(libs.bundles.testTools) diff --git a/features/dd-sdk-android-session-replay-compose/build.gradle.kts b/features/dd-sdk-android-session-replay-compose/build.gradle.kts index ceb42ab2a9..dbd4cbc4a3 100644 --- a/features/dd-sdk-android-session-replay-compose/build.gradle.kts +++ b/features/dd-sdk-android-session-replay-compose/build.gradle.kts @@ -56,7 +56,7 @@ android { dependencies { api(project(":features:dd-sdk-android-session-replay")) implementation(project(":dd-sdk-android-internal")) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) implementation(libs.gson) implementation(platform(libs.androidXComposeBom)) diff --git a/features/dd-sdk-android-session-replay-material/build.gradle.kts b/features/dd-sdk-android-session-replay-material/build.gradle.kts index 79734bd89a..a19f771344 100644 --- a/features/dd-sdk-android-session-replay-material/build.gradle.kts +++ b/features/dd-sdk-android-session-replay-material/build.gradle.kts @@ -48,7 +48,7 @@ android { dependencies { api(project(":features:dd-sdk-android-session-replay")) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) implementation(libs.gson) implementation(libs.googleMaterial) diff --git a/features/dd-sdk-android-session-replay/build.gradle.kts b/features/dd-sdk-android-session-replay/build.gradle.kts index 99f3b6bc68..8fba8b2e29 100644 --- a/features/dd-sdk-android-session-replay/build.gradle.kts +++ b/features/dd-sdk-android-session-replay/build.gradle.kts @@ -56,7 +56,7 @@ dependencies { api(project(":dd-sdk-android-core")) implementation(project(":dd-sdk-android-internal")) implementation(libs.okHttp) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) implementation(libs.gson) implementation(libs.androidXAppCompat) diff --git a/features/dd-sdk-android-trace-api/build.gradle.kts b/features/dd-sdk-android-trace-api/build.gradle.kts index f673240bd3..dbe5eca135 100644 --- a/features/dd-sdk-android-trace-api/build.gradle.kts +++ b/features/dd-sdk-android-trace-api/build.gradle.kts @@ -47,7 +47,7 @@ android { dependencies { api(project(":dd-sdk-android-core")) implementation(project(":dd-sdk-android-internal")) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) implementation(libs.gson) implementation(libs.androidXAnnotation) implementation(libs.bundles.traceCore) diff --git a/features/dd-sdk-android-trace-internal/build.gradle.kts b/features/dd-sdk-android-trace-internal/build.gradle.kts index d00ac2da57..939a62b56c 100644 --- a/features/dd-sdk-android-trace-internal/build.gradle.kts +++ b/features/dd-sdk-android-trace-internal/build.gradle.kts @@ -47,7 +47,7 @@ android { dependencies { api(project(":dd-sdk-android-core")) implementation(project(":dd-sdk-android-internal")) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) implementation(libs.gson) implementation(libs.androidXAnnotation) implementation(libs.bundles.traceCore) diff --git a/features/dd-sdk-android-trace-otel/build.gradle.kts b/features/dd-sdk-android-trace-otel/build.gradle.kts index fd3daf1ecf..c2141a6b9b 100644 --- a/features/dd-sdk-android-trace-otel/build.gradle.kts +++ b/features/dd-sdk-android-trace-otel/build.gradle.kts @@ -57,7 +57,7 @@ android { dependencies { api(libs.openTelemetryApi) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) implementation(libs.androidXAnnotation) api(project(":dd-sdk-android-core")) diff --git a/features/dd-sdk-android-trace/build.gradle.kts b/features/dd-sdk-android-trace/build.gradle.kts index f4b0f81327..a2a8f3934b 100644 --- a/features/dd-sdk-android-trace/build.gradle.kts +++ b/features/dd-sdk-android-trace/build.gradle.kts @@ -59,7 +59,7 @@ dependencies { api(project(":features:dd-sdk-android-trace-api")) implementation(project(":dd-sdk-android-internal")) implementation(project(":features:dd-sdk-android-trace-internal")) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) implementation(libs.gson) implementation(libs.androidXAnnotation) implementation(libs.bundles.traceCore) @@ -86,7 +86,7 @@ dependencies { // Test Fixtures testFixturesImplementation(libs.gson) - testFixturesImplementation(libs.kotlin) + testFixturesImplementation(libs.kotlinRuntime) testFixturesImplementation(libs.okHttp) testFixturesImplementation(libs.bundles.jUnit5) testFixturesImplementation(libs.bundles.testTools) diff --git a/features/dd-sdk-android-webview/build.gradle.kts b/features/dd-sdk-android-webview/build.gradle.kts index 9cf56bac9d..bfc47c51b4 100644 --- a/features/dd-sdk-android-webview/build.gradle.kts +++ b/features/dd-sdk-android-webview/build.gradle.kts @@ -47,7 +47,7 @@ android { dependencies { api(project(":dd-sdk-android-core")) implementation(project(":dd-sdk-android-internal")) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) implementation(libs.gson) implementation(libs.androidXAnnotation) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 36e86e697e..0697d84b36 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,17 +4,11 @@ apollo = "4.3.3" cronetApi = "143.7445.0" cronetPlayServices = "18.1.1" gson = "2.10.1" -kotlin = "2.0.21" -# Deliberately not `version.ref = "kotlin"`: this must match the Kotlin Gradle Plugin that is actually -# loaded, which is AGP's. `androidToolsPlugin` (below) depends on KGP 2.2.10, and that outranks the -# `kotlin` version above in conflict resolution (`kotlin-gradle-plugin:2.0.21 -> 2.2.10`). -# The Compose *compiler* is always resolved at the loaded KGP's version and never at this one, so the -# two drift apart silently. An older plugin against a newer compiler passes options the compiler has -# since deprecated (RUM-18191, `generateFunctionKeyMetaClasses`), and the version also decides which -# options are sent at all — 2.2.10 turns on `sourceInformation`, which changes generated Compose code. -# Re-check this whenever `androidToolsPlugin` moves. -composeCompilerPlugin = "2.2.10" -kotlinSP = "2.0.21-1.0.28" +# We have 2 different versions of Kotlin: one is for the buildscript classpath - Gradle Plugin, +# Buildscript runtime dependency; another is for the SDK compile/runtime classpath. +kotlinBuildscript = "2.2.10" +kotlinRuntime = "2.0.21" +kotlinSP = "2.2.10-2.0.2" kronosNTP = "0.0.1-alpha11" kotlinxSerialization = "1.6.3" okHttp = "4.12.0" @@ -67,7 +61,7 @@ msgPackCore = "0.9.12" protobufJava = "4.35.0" sketchesJava = "0.8.3" kover = "0.9.8" -kspTesting = "1.5.0" +kspTesting = "0.8.0" # Tools detekt = "1.23.8" @@ -81,7 +75,7 @@ androidLint = "31.0.2" nexusPublishGradlePlugin = "2.0.0" datadogPlugin = "1.31.0" -kotlinPoet = "1.14.2" +kotlinPoet = "2.2.0" jsonSchemaValidator = "1.14.6" binaryCompatibility = "0.18.1" dependencyLicense = "0.7.0" @@ -122,7 +116,7 @@ material3Android = "1.1.2" # Gradle version catalog doesn't allow names to end with plugin, that is why strange names androidToolsGradlePluginApi = { module = "com.android.tools.build:gradle-api", version.ref = "androidToolsPlugin" } apolloRuntime = { module = "com.apollographql.apollo:apollo-runtime", version.ref = "apollo" } -kotlinGradlePluginApi = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin-api", version.ref = "kotlin" } +kotlinGradlePluginApi = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin-api", version.ref = "kotlinBuildscript" } dokkaGradlePlugin = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "dokka" } detektGradlePlugin = { module = "io.gitlab.arturbosch.detekt:detekt-gradle-plugin", version.ref = "detekt" } unmockGradlePlugin = { module = "com.github.bjoernq:unmockplugin", version.ref = "unmock" } @@ -133,7 +127,8 @@ roomCompiler = { module = "androidx.room:room-compiler", version.ref = "room" } # Common -kotlin = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" } +kotlinRuntime = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlinRuntime" } +kotlinBuildscript = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlinBuildscript" } kotlinxSerializationJson = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerialization" } okHttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okHttp" } @@ -215,8 +210,8 @@ mockitoKotlin = { module = "org.mockito.kotlin:mockito-kotlin", version.ref = "m msgPackCore = { module = "org.msgpack:msgpack-core", version.ref = "msgPackCore" } protobufJava = { module = "com.google.protobuf:protobuf-java", version.ref = "protobufJava" } sketchesJava = { module = "com.datadoghq:sketches-java", version.ref = "sketchesJava" } -kotlinReflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" } -kspTesting = { module = "com.github.tschuchortdev:kotlin-compile-testing-ksp", version.ref = "kspTesting" } +kotlinReflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlinRuntime" } +kspTesting = { module = "dev.zacsweers.kctfork:ksp", version.ref = "kspTesting" } androidLintTests = { module = "com.android.tools.lint:lint-tests", version.ref = "androidLint" } systemStubsJupiter = { module = "uk.org.webcompere:system-stubs-jupiter", version.ref = "systemStubsJupiter" } @@ -232,7 +227,7 @@ androidLintChecks = { module = "com.android.tools.lint:lint-checks", version.ref kotlinPoet = { module = "com.squareup:kotlinpoet", version.ref = "kotlinPoet" } kotlinPoetKsp = { module = "com.squareup:kotlinpoet-ksp", version.ref = "kotlinPoet" } kotlinSP = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "kotlinSP" } -kotlinCompilerEmbeddable = { module = "org.jetbrains.kotlin:kotlin-compiler-embeddable", version.ref = "kotlin" } +kotlinCompilerEmbeddable = { module = "org.jetbrains.kotlin:kotlin-compiler-embeddable", version.ref = "kotlinBuildscript" } jsonSchemaValidator = { module = "com.github.erosb:everit-json-schema", version.ref = "jsonSchemaValidator" } kotlinXmlBuilder = { module = "org.redundent:kotlin-xml-builder", version.ref = "kotlinXmlBuilder" } @@ -308,8 +303,7 @@ testTools = [ "elmyrInject", "elmyrJUnit5", "elmyrJVM", - "mockitoKotlin", - "kotlinReflect" + "mockitoKotlin" ] androidXNavigation = [ @@ -386,16 +380,16 @@ traceCore = [ [plugins] nexusPublishGradlePlugin = { id = "io.github.gradle-nexus.publish-plugin", version.ref = "nexusPublishGradlePlugin" } datadogGradlePlugin = { id = "com.datadoghq.dd-sdk-android-gradle-plugin", version.ref = "datadogPlugin" } -composeCompilerPlugin = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "composeCompilerPlugin" } +composeCompilerPlugin = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlinBuildscript" } apolloPlugin = { id = "com.apollographql.apollo", version.ref = "apollo" } dependencyLicenseGradlePlugin = { id = "com.datadoghq.dependency-license", version.ref = "dependencyLicense" } -kotlinAndroidPlugin = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +kotlinAndroidPlugin = { id = "org.jetbrains.kotlin.android", version.ref = "kotlinBuildscript" } androidLibraryPlugin = { id = "com.android.library", version.ref = "androidToolsPlugin" } kotlinSPGradlePlugin = { id = "com.google.devtools.ksp", version.ref = "kotlinSP" } dokkaGradlePlugin = { id = "org.jetbrains.dokka", version.ref = "dokka" } sqlDelightGradlePlugin = { id = "com.squareup.sqldelight", version.ref = "sqlDelight" } binaryCompatibilityGradlePlugin = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binaryCompatibility" } -kotlinxSerializationPlugin = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } +kotlinxSerializationPlugin = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlinBuildscript" } koverPlugin = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" } ktlintGradlePlugin = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlintGradlePlugin" } detektGradlePlugin = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" } diff --git a/instrumented/integration/build.gradle.kts b/instrumented/integration/build.gradle.kts index dad57c8b5e..dae2e8187c 100644 --- a/instrumented/integration/build.gradle.kts +++ b/instrumented/integration/build.gradle.kts @@ -115,7 +115,7 @@ dependencies { compileOnly(libs.androidXComposeRuntime) implementation(libs.gson) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) implementation(libs.bundles.androidXSupportBase) implementation(libs.elmyr) implementation(libs.leakCanaryAndroid) diff --git a/integrations/dd-sdk-android-apollo/build.gradle.kts b/integrations/dd-sdk-android-apollo/build.gradle.kts index 62aeaad12b..3e363d59e6 100644 --- a/integrations/dd-sdk-android-apollo/build.gradle.kts +++ b/integrations/dd-sdk-android-apollo/build.gradle.kts @@ -45,7 +45,7 @@ android { dependencies { implementation(project(":dd-sdk-android-internal")) implementation(libs.apolloRuntime) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) implementation(libs.okHttp) testImplementation(project(":tools:unit")) { diff --git a/integrations/dd-sdk-android-coil/build.gradle.kts b/integrations/dd-sdk-android-coil/build.gradle.kts index fd55fff33b..0de693f7dd 100644 --- a/integrations/dd-sdk-android-coil/build.gradle.kts +++ b/integrations/dd-sdk-android-coil/build.gradle.kts @@ -44,7 +44,7 @@ android { dependencies { implementation(project(":features:dd-sdk-android-rum")) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) implementation(libs.okHttp) implementation(libs.coil) diff --git a/integrations/dd-sdk-android-coil3/build.gradle.kts b/integrations/dd-sdk-android-coil3/build.gradle.kts index 4980e54251..3a3f0ebd1b 100644 --- a/integrations/dd-sdk-android-coil3/build.gradle.kts +++ b/integrations/dd-sdk-android-coil3/build.gradle.kts @@ -44,7 +44,7 @@ android { dependencies { implementation(project(":features:dd-sdk-android-rum")) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) implementation(libs.okHttp) implementation(libs.coil3) implementation(libs.coil3NetworkOkHttp) diff --git a/integrations/dd-sdk-android-compose/build.gradle.kts b/integrations/dd-sdk-android-compose/build.gradle.kts index 3fdaa27643..ad25fac488 100644 --- a/integrations/dd-sdk-android-compose/build.gradle.kts +++ b/integrations/dd-sdk-android-compose/build.gradle.kts @@ -54,7 +54,7 @@ android { dependencies { implementation(project(":dd-sdk-android-internal")) implementation(project(":features:dd-sdk-android-rum")) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) implementation(platform(libs.androidXComposeBom)) implementation(libs.androidXComposeRuntime) diff --git a/integrations/dd-sdk-android-cronet/build.gradle.kts b/integrations/dd-sdk-android-cronet/build.gradle.kts index 80e1e34e0e..7dbe7d5156 100644 --- a/integrations/dd-sdk-android-cronet/build.gradle.kts +++ b/integrations/dd-sdk-android-cronet/build.gradle.kts @@ -50,7 +50,7 @@ android { dependencies { api(libs.cronetApi) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) implementation(libs.androidXAnnotation) implementation(project(":dd-sdk-android-internal")) diff --git a/integrations/dd-sdk-android-fresco/build.gradle.kts b/integrations/dd-sdk-android-fresco/build.gradle.kts index 0c398a1178..af30ad5e12 100644 --- a/integrations/dd-sdk-android-fresco/build.gradle.kts +++ b/integrations/dd-sdk-android-fresco/build.gradle.kts @@ -44,7 +44,7 @@ android { dependencies { implementation(project(":features:dd-sdk-android-rum")) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) implementation(libs.okHttp) implementation(libs.bundles.fresco) diff --git a/integrations/dd-sdk-android-glide/build.gradle.kts b/integrations/dd-sdk-android-glide/build.gradle.kts index 58cfc9b72e..821b38c815 100644 --- a/integrations/dd-sdk-android-glide/build.gradle.kts +++ b/integrations/dd-sdk-android-glide/build.gradle.kts @@ -45,7 +45,7 @@ android { dependencies { implementation(project(":features:dd-sdk-android-rum")) implementation(project(":integrations:dd-sdk-android-okhttp")) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) implementation(libs.okHttp) implementation(libs.bundles.glide) diff --git a/integrations/dd-sdk-android-okhttp-otel/build.gradle.kts b/integrations/dd-sdk-android-okhttp-otel/build.gradle.kts index d03b0e977c..3926a7ab50 100644 --- a/integrations/dd-sdk-android-okhttp-otel/build.gradle.kts +++ b/integrations/dd-sdk-android-okhttp-otel/build.gradle.kts @@ -44,7 +44,7 @@ android { dependencies { implementation(libs.okHttp) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) implementation(project(":features:dd-sdk-android-trace")) implementation(project(":integrations:dd-sdk-android-okhttp")) diff --git a/integrations/dd-sdk-android-okhttp/build.gradle.kts b/integrations/dd-sdk-android-okhttp/build.gradle.kts index 1fed1bcca6..914fd14f76 100644 --- a/integrations/dd-sdk-android-okhttp/build.gradle.kts +++ b/integrations/dd-sdk-android-okhttp/build.gradle.kts @@ -47,7 +47,7 @@ android { } dependencies { - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) implementation(libs.okHttp) implementation(libs.androidXAnnotation) implementation(project(":dd-sdk-android-internal")) diff --git a/integrations/dd-sdk-android-rum-coroutines/build.gradle.kts b/integrations/dd-sdk-android-rum-coroutines/build.gradle.kts index ee1664b87c..a24db0935b 100644 --- a/integrations/dd-sdk-android-rum-coroutines/build.gradle.kts +++ b/integrations/dd-sdk-android-rum-coroutines/build.gradle.kts @@ -43,7 +43,7 @@ android { dependencies { implementation(project(":features:dd-sdk-android-rum")) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) implementation(libs.coroutinesCore) testImplementation(project(":tools:unit")) { diff --git a/integrations/dd-sdk-android-rx/build.gradle.kts b/integrations/dd-sdk-android-rx/build.gradle.kts index ee935726c2..4d24341303 100644 --- a/integrations/dd-sdk-android-rx/build.gradle.kts +++ b/integrations/dd-sdk-android-rx/build.gradle.kts @@ -44,7 +44,7 @@ android { dependencies { implementation(project(":features:dd-sdk-android-rum")) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) implementation(libs.okHttp) implementation(libs.rxJava3) diff --git a/integrations/dd-sdk-android-sqldelight/build.gradle.kts b/integrations/dd-sdk-android-sqldelight/build.gradle.kts index d8de3718a0..b60b540901 100644 --- a/integrations/dd-sdk-android-sqldelight/build.gradle.kts +++ b/integrations/dd-sdk-android-sqldelight/build.gradle.kts @@ -45,7 +45,7 @@ android { dependencies { implementation(project(":features:dd-sdk-android-trace")) implementation(project(":features:dd-sdk-android-rum")) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) implementation(libs.okHttp) implementation(libs.sqlDelight) diff --git a/integrations/dd-sdk-android-timber/build.gradle.kts b/integrations/dd-sdk-android-timber/build.gradle.kts index e929937ded..033f1708b6 100644 --- a/integrations/dd-sdk-android-timber/build.gradle.kts +++ b/integrations/dd-sdk-android-timber/build.gradle.kts @@ -44,7 +44,7 @@ android { dependencies { implementation(project(":features:dd-sdk-android-logs")) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) implementation(libs.timber) testImplementation(project(":tools:unit")) { diff --git a/integrations/dd-sdk-android-trace-coroutines/build.gradle.kts b/integrations/dd-sdk-android-trace-coroutines/build.gradle.kts index 30a59d4f1c..b4bb069f31 100644 --- a/integrations/dd-sdk-android-trace-coroutines/build.gradle.kts +++ b/integrations/dd-sdk-android-trace-coroutines/build.gradle.kts @@ -42,7 +42,7 @@ android { } dependencies { - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) implementation(libs.coroutinesCore) implementation(project(":features:dd-sdk-android-trace")) diff --git a/integrations/dd-sdk-android-tv/build.gradle.kts b/integrations/dd-sdk-android-tv/build.gradle.kts index 3c4a35d1a0..ad549bc452 100644 --- a/integrations/dd-sdk-android-tv/build.gradle.kts +++ b/integrations/dd-sdk-android-tv/build.gradle.kts @@ -44,7 +44,7 @@ android { dependencies { implementation(project(":features:dd-sdk-android-rum")) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) implementation(libs.androidXLeanback) testImplementation(project(":tools:unit")) { diff --git a/reliability/core-it/build.gradle.kts b/reliability/core-it/build.gradle.kts index 7c73555cd8..f3a4c540a0 100644 --- a/reliability/core-it/build.gradle.kts +++ b/reliability/core-it/build.gradle.kts @@ -69,7 +69,7 @@ dependencies { coreLibraryDesugaring(libs.androidDesugaringSdk) } implementation(project(":dd-sdk-android-core")) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) // Testing androidTestImplementation(project(":tools:unit")) { diff --git a/reliability/single-fit/logs/build.gradle.kts b/reliability/single-fit/logs/build.gradle.kts index 7712775b07..5266333154 100644 --- a/reliability/single-fit/logs/build.gradle.kts +++ b/reliability/single-fit/logs/build.gradle.kts @@ -31,7 +31,7 @@ android { dependencies { implementation(project(":dd-sdk-android-core")) implementation(project(":features:dd-sdk-android-logs")) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) // Testing testImplementation(project(":tools:unit")) { diff --git a/reliability/single-fit/okhttp/build.gradle.kts b/reliability/single-fit/okhttp/build.gradle.kts index 7f32d061d8..88d4ea8b74 100644 --- a/reliability/single-fit/okhttp/build.gradle.kts +++ b/reliability/single-fit/okhttp/build.gradle.kts @@ -34,7 +34,7 @@ dependencies { implementation(project(":features:dd-sdk-android-trace-otel")) implementation(project(":integrations:dd-sdk-android-okhttp")) implementation(project(":integrations:dd-sdk-android-okhttp-otel")) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) // Testing testImplementation(project(":tools:unit")) { diff --git a/reliability/single-fit/profiling/build.gradle.kts b/reliability/single-fit/profiling/build.gradle.kts index 2543b6f133..e26fb24a48 100644 --- a/reliability/single-fit/profiling/build.gradle.kts +++ b/reliability/single-fit/profiling/build.gradle.kts @@ -33,7 +33,7 @@ dependencies { implementation(project(":dd-sdk-android-internal")) implementation(project(":features:dd-sdk-android-rum")) implementation(project(":features:dd-sdk-android-profiling")) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) // Testing testImplementation(project(":tools:unit")) { diff --git a/reliability/single-fit/rum/build.gradle.kts b/reliability/single-fit/rum/build.gradle.kts index 8feaf98da5..3e466073d2 100644 --- a/reliability/single-fit/rum/build.gradle.kts +++ b/reliability/single-fit/rum/build.gradle.kts @@ -32,7 +32,7 @@ dependencies { implementation(project(":dd-sdk-android-core")) implementation(project(":dd-sdk-android-internal")) implementation(project(":features:dd-sdk-android-rum")) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) implementation(libs.bundles.androidXNavigation) // Testing diff --git a/reliability/single-fit/trace/build.gradle.kts b/reliability/single-fit/trace/build.gradle.kts index d9c2f1bd6e..6e962cfb5b 100644 --- a/reliability/single-fit/trace/build.gradle.kts +++ b/reliability/single-fit/trace/build.gradle.kts @@ -36,7 +36,7 @@ dependencies { implementation(project(":dd-sdk-android-core")) implementation(project(":features:dd-sdk-android-trace")) implementation(project(":features:dd-sdk-android-trace-otel")) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) // Desugaring SDK coreLibraryDesugaring(libs.androidDesugaringSdk) diff --git a/reliability/stub-core/build.gradle.kts b/reliability/stub-core/build.gradle.kts index d1e7b1b909..0579761c11 100644 --- a/reliability/stub-core/build.gradle.kts +++ b/reliability/stub-core/build.gradle.kts @@ -31,7 +31,7 @@ android { dependencies { implementation(project(":dd-sdk-android-internal")) implementation(project(":dd-sdk-android-core")) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) // Testing implementation(project(":tools:unit")) { diff --git a/reliability/stub-feature/build.gradle.kts b/reliability/stub-feature/build.gradle.kts index 0ed3a6fd34..0e8f9b30b2 100644 --- a/reliability/stub-feature/build.gradle.kts +++ b/reliability/stub-feature/build.gradle.kts @@ -27,7 +27,7 @@ android { dependencies { implementation(project(":dd-sdk-android-core")) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) // Testing implementation(libs.bundles.jUnit5) diff --git a/reliability/with-backend/build.gradle.kts b/reliability/with-backend/build.gradle.kts index fc00fa16c6..3b789da1c2 100644 --- a/reliability/with-backend/build.gradle.kts +++ b/reliability/with-backend/build.gradle.kts @@ -68,7 +68,7 @@ dependencies { if (project.hasProperty(com.datadog.gradle.Properties.USE_DESUGARING)) { coreLibraryDesugaring(libs.androidDesugaringSdk) } - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) // Testing androidTestImplementation(libs.assertJ) diff --git a/sample/automotive/build.gradle.kts b/sample/automotive/build.gradle.kts index 7e79069d1b..102353cc8b 100644 --- a/sample/automotive/build.gradle.kts +++ b/sample/automotive/build.gradle.kts @@ -70,7 +70,7 @@ dependencies { implementation(project(":features:dd-sdk-android-logs")) implementation(project(":features:dd-sdk-android-rum")) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) // Android dependencies implementation(libs.androidXCoreKtx) diff --git a/sample/benchmark/build.gradle.kts b/sample/benchmark/build.gradle.kts index 338c2e99ef..6de66a1624 100644 --- a/sample/benchmark/build.gradle.kts +++ b/sample/benchmark/build.gradle.kts @@ -89,7 +89,7 @@ datadog { dependencies { - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) // Android dependencies implementation(libs.adapterDelegatesViewBinding) diff --git a/sample/kotlin/build.gradle.kts b/sample/kotlin/build.gradle.kts index 009e1b91d7..b0d59427b3 100644 --- a/sample/kotlin/build.gradle.kts +++ b/sample/kotlin/build.gradle.kts @@ -180,7 +180,7 @@ dependencies { // Sample Vendor Library implementation(project(":sample:vendor-lib")) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) // Android dependencies implementation(libs.cronetPlayServices) diff --git a/sample/tv/build.gradle.kts b/sample/tv/build.gradle.kts index b985916ba1..927094d38f 100644 --- a/sample/tv/build.gradle.kts +++ b/sample/tv/build.gradle.kts @@ -79,7 +79,7 @@ dependencies { implementation(project(":integrations:dd-sdk-android-timber")) implementation(project(":integrations:dd-sdk-android-tv")) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) // Android dependencies implementation(libs.androidXCore) diff --git a/sample/vendor-lib/build.gradle.kts b/sample/vendor-lib/build.gradle.kts index 14ba5c5cdf..def41692ac 100644 --- a/sample/vendor-lib/build.gradle.kts +++ b/sample/vendor-lib/build.gradle.kts @@ -77,7 +77,7 @@ dependencies { implementation(project(":features:dd-sdk-android-trace-otel")) implementation(project(":integrations:dd-sdk-android-okhttp")) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) // Ktor (local web server) implementation(libs.bundles.ktor) diff --git a/tools/benchmark/build.gradle.kts b/tools/benchmark/build.gradle.kts index 45e986ed5a..24e69289b2 100644 --- a/tools/benchmark/build.gradle.kts +++ b/tools/benchmark/build.gradle.kts @@ -38,7 +38,7 @@ android { dependencies { implementation(project(":dd-sdk-android-core")) implementation(project(":dd-sdk-android-internal")) - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) implementation(libs.okHttp) implementation(libs.openTelemetryApi) implementation(libs.openTelemetrySdk) diff --git a/tools/detekt/build.gradle.kts b/tools/detekt/build.gradle.kts index 583797fc9b..19b4a79cbc 100644 --- a/tools/detekt/build.gradle.kts +++ b/tools/detekt/build.gradle.kts @@ -14,8 +14,8 @@ plugins { } dependencies { - implementation(libs.kotlin) - implementation(libs.kotlinReflect) + // pin it temporarily to Kotlin Runtime, because Detekt 1.23.8 is not capable processing Kotlin 2.2 stdlib + implementation(libs.kotlinRuntime) implementation(libs.androidXAnnotation) compileOnly(libs.detektApi) diff --git a/tools/javabackport/build.gradle.kts b/tools/javabackport/build.gradle.kts index a70eff10bb..7cc9e5eee5 100644 --- a/tools/javabackport/build.gradle.kts +++ b/tools/javabackport/build.gradle.kts @@ -14,7 +14,7 @@ plugins { } dependencies { - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) } datadogBuild { diff --git a/tools/lint/build.gradle.kts b/tools/lint/build.gradle.kts index ee4f632372..d570c2383c 100644 --- a/tools/lint/build.gradle.kts +++ b/tools/lint/build.gradle.kts @@ -17,7 +17,8 @@ plugins { } dependencies { - compileOnly(libs.kotlin) + // using Runtime version here, because this is a rule shipped with SDK + compileOnly(libs.kotlinRuntime) compileOnly(libs.androidLintApi) compileOnly(libs.androidLintChecks) diff --git a/tools/noopfactory/build.gradle.kts b/tools/noopfactory/build.gradle.kts index 064f370fa4..2e09fbb00b 100644 --- a/tools/noopfactory/build.gradle.kts +++ b/tools/noopfactory/build.gradle.kts @@ -5,6 +5,7 @@ */ import com.datadog.gradle.config.taskConfig +import org.jetbrains.kotlin.gradle.dsl.KotlinVersion import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { @@ -17,8 +18,7 @@ plugins { } dependencies { - implementation(libs.kotlin) - implementation(libs.kotlinReflect) + implementation(libs.kotlinBuildscript) implementation(libs.kotlinSP) implementation(libs.kotlinPoet) implementation(libs.kotlinPoetKsp) @@ -29,7 +29,10 @@ dependencies { } datadogBuild { - applyKotlinConfig() + applyKotlinConfig( + kotlinApiVersion = KotlinVersion.KOTLIN_2_2, + kotlinLanguageVersion = KotlinVersion.KOTLIN_2_2 + ) applyJunitConfig() } diff --git a/tools/noopfactory/src/test/kotlin/com/datadog/tools/noopfactory/NoOpFactoryProviderTest.kt b/tools/noopfactory/src/test/kotlin/com/datadog/tools/noopfactory/NoOpFactoryProviderTest.kt index 1b43c8331b..1f0320e82d 100644 --- a/tools/noopfactory/src/test/kotlin/com/datadog/tools/noopfactory/NoOpFactoryProviderTest.kt +++ b/tools/noopfactory/src/test/kotlin/com/datadog/tools/noopfactory/NoOpFactoryProviderTest.kt @@ -11,10 +11,12 @@ import com.google.devtools.ksp.processing.Dependencies import com.google.devtools.ksp.processing.SymbolProcessor import com.google.devtools.ksp.processing.SymbolProcessorEnvironment import com.google.devtools.ksp.processing.SymbolProcessorProvider +import com.tschuchort.compiletesting.JvmCompilationResult import com.tschuchort.compiletesting.KotlinCompilation import com.tschuchort.compiletesting.SourceFile -import com.tschuchort.compiletesting.symbolProcessorProviders +import com.tschuchort.compiletesting.configureKsp import org.assertj.core.api.Assertions.assertThat +import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.CsvSource @@ -22,6 +24,7 @@ import java.io.File import java.io.OutputStream import java.nio.file.FileAlreadyExistsException +@OptIn(ExperimentalCompilerApi::class) internal class NoOpFactoryProviderTest { @ParameterizedTest @@ -50,13 +53,18 @@ internal class NoOpFactoryProviderTest { val srcFile = File(checkNotNull(javaClass.getResource("/src/$srcFileName")).file) val experimentalApiAnnotationFile = File(checkNotNull(javaClass.getResource("/src/ExperimentalApi.kt")).file) val genFile = File(checkNotNull(javaClass.getResource("/gen/$genFileName")).file) - val kotlinSource = SourceFile.fromPath(srcFile) - val experimentalApiAnnotationSource = SourceFile.fromPath(experimentalApiAnnotationFile) + val kotlinSource = SourceFile.kotlin(srcFile.name, srcFile.readText()) + val experimentalApiAnnotationSource = SourceFile.kotlin( + experimentalApiAnnotationFile.name, + experimentalApiAnnotationFile.readText() + ) val result = KotlinCompilation().apply { inheritClassPath = true sources = listOf(kotlinSource, experimentalApiAnnotationSource) - symbolProcessorProviders = listOf(NoOpFactoryProvider()) + configureKsp(useKsp2 = true) { + symbolProcessorProviders += NoOpFactoryProvider() + } }.compile() assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK) @@ -72,12 +80,14 @@ internal class NoOpFactoryProviderTest { ) fun `ignores invalid types`(srcFileName: String) { val srcFile = File(checkNotNull(javaClass.getResource("/src/$srcFileName")).file) - val kotlinSource = SourceFile.fromPath(srcFile) + val kotlinSource = SourceFile.kotlin(srcFile.name, srcFile.readText()) val result = KotlinCompilation().apply { inheritClassPath = true sources = listOf(kotlinSource) - symbolProcessorProviders = listOf(NoOpFactoryProvider()) + configureKsp(useKsp2 = true) { + symbolProcessorProviders += NoOpFactoryProvider() + } }.compile() assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK) @@ -100,12 +110,14 @@ internal class NoOpFactoryProviderTest { ) fun `ignores interfaces with restricted visibility`(srcFileName: String, noOpFileName: String) { val srcFile = File(checkNotNull(javaClass.getResource("/src/$srcFileName")).file) - val kotlinSource = SourceFile.fromPath(srcFile) + val kotlinSource = SourceFile.kotlin(srcFile.name, srcFile.readText()) val result = KotlinCompilation().apply { inheritClassPath = true sources = listOf(kotlinSource) - symbolProcessorProviders = listOf(NoOpFactoryProvider()) + configureKsp(useKsp2 = true) { + symbolProcessorProviders += NoOpFactoryProvider() + } }.compile() assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK) @@ -120,8 +132,10 @@ internal class NoOpFactoryProviderTest { // When val result = KotlinCompilation().apply { inheritClassPath = true - sources = listOf(SourceFile.fromPath(srcFile)) - symbolProcessorProviders = listOf(NoOpFactoryProvider()) + sources = listOf(SourceFile.kotlin(srcFile.name, srcFile.readText())) + configureKsp(useKsp2 = true) { + symbolProcessorProviders += NoOpFactoryProvider() + } }.compile() // Then @@ -152,8 +166,10 @@ internal class NoOpFactoryProviderTest { // When val result = KotlinCompilation().apply { inheritClassPath = true - sources = listOf(SourceFile.fromPath(srcFile)) - symbolProcessorProviders = listOf(NoOpFactoryProvider()) + sources = listOf(SourceFile.kotlin(srcFile.name, srcFile.readText())) + configureKsp(useKsp2 = true) { + symbolProcessorProviders += NoOpFactoryProvider() + } }.compile() // Then @@ -183,8 +199,10 @@ internal class NoOpFactoryProviderTest { // When val result = KotlinCompilation().apply { inheritClassPath = true - sources = listOf(SourceFile.fromPath(srcFile)) - symbolProcessorProviders = listOf(NoOpFactoryProvider()) + sources = listOf(SourceFile.kotlin(srcFile.name, srcFile.readText())) + configureKsp(useKsp2 = true) { + symbolProcessorProviders += NoOpFactoryProvider() + } }.compile() // Then @@ -213,8 +231,10 @@ internal class NoOpFactoryProviderTest { // When val result = KotlinCompilation().apply { inheritClassPath = true - sources = listOf(SourceFile.fromPath(srcFile)) - symbolProcessorProviders = listOf(NoOpFactoryProvider()) + sources = listOf(SourceFile.kotlin(srcFile.name, srcFile.readText())) + configureKsp(useKsp2 = true) { + symbolProcessorProviders += NoOpFactoryProvider() + } }.compile() // Then @@ -234,8 +254,10 @@ internal class NoOpFactoryProviderTest { // When val result = KotlinCompilation().apply { inheritClassPath = true - sources = listOf(SourceFile.fromPath(srcFile)) - symbolProcessorProviders = listOf(ThrowingCodeGeneratorProvider()) + sources = listOf(SourceFile.kotlin(srcFile.name, srcFile.readText())) + configureKsp(useKsp2 = true) { + symbolProcessorProviders += ThrowingCodeGeneratorProvider() + } }.compile() // Then @@ -255,8 +277,10 @@ internal class NoOpFactoryProviderTest { // When val result = KotlinCompilation().apply { inheritClassPath = true - sources = listOf(SourceFile.fromPath(srcFile)) - symbolProcessorProviders = listOf(ThrowingCodeGeneratorProvider()) + sources = listOf(SourceFile.kotlin(srcFile.name, srcFile.readText())) + configureKsp(useKsp2 = true) { + symbolProcessorProviders += ThrowingCodeGeneratorProvider() + } }.compile() // Then @@ -268,14 +292,14 @@ internal class NoOpFactoryProviderTest { ) } - private fun KotlinCompilation.Result.assertNothingGenerated( + private fun JvmCompilationResult.assertNothingGenerated( generatedFileName: String ) { assertThat(sourceFor(generatedFileName)) .isNull() } - private fun KotlinCompilation.Result.assertGeneratedFileEquals( + private fun JvmCompilationResult.assertGeneratedFileEquals( generatedFileName: String, expectedContent: String ) { @@ -283,13 +307,13 @@ internal class NoOpFactoryProviderTest { .isEqualTo(expectedContent) } - private fun KotlinCompilation.Result.sourceFor(fileName: String): String? { + private fun JvmCompilationResult.sourceFor(fileName: String): String? { val kspGeneratedSources = getKspGeneratedSources() return kspGeneratedSources.find { it.name == fileName } ?.readText() } - private fun KotlinCompilation.Result.getKspGeneratedSources(): List { + private fun JvmCompilationResult.getKspGeneratedSources(): List { val workingDir = outputDirectory.parentFile val kspWorkingDir = workingDir.resolve("ksp") val kspGeneratedDir = kspWorkingDir.resolve("sources") diff --git a/tools/noopfactory/src/test/resources/gen/NoOpEnumInterface.kt b/tools/noopfactory/src/test/resources/gen/NoOpEnumInterface.kt index 7fb65b6107..3b2d70c3c9 100644 --- a/tools/noopfactory/src/test/resources/gen/NoOpEnumInterface.kt +++ b/tools/noopfactory/src/test/resources/gen/NoOpEnumInterface.kt @@ -7,5 +7,5 @@ import kotlin.Suppress internal class NoOpEnumInterface : EnumInterface { override val weekDay: EnumInterface.WeekDay = EnumInterface.WeekDay.MON - override fun getWeekDay(): EnumInterface.WeekDay = EnumInterface.WeekDay.MON + override fun getDay(): EnumInterface.WeekDay = EnumInterface.WeekDay.MON } diff --git a/tools/noopfactory/src/test/resources/src/EnumInterface.kt b/tools/noopfactory/src/test/resources/src/EnumInterface.kt index 09cb057148..27a4d2f38a 100644 --- a/tools/noopfactory/src/test/resources/src/EnumInterface.kt +++ b/tools/noopfactory/src/test/resources/src/EnumInterface.kt @@ -6,7 +6,7 @@ import com.datadog.tools.annotation.NoOpImplementation interface EnumInterface { enum class WeekDay { MON, TUE, WED, THU, FRI, SAT, SUN } - fun getWeekDay(): WeekDay + fun getDay(): WeekDay val weekDay: WeekDay } diff --git a/tools/unit/build.gradle.kts b/tools/unit/build.gradle.kts index 37293e275b..76c3ee16d5 100644 --- a/tools/unit/build.gradle.kts +++ b/tools/unit/build.gradle.kts @@ -64,7 +64,8 @@ android { } dependencies { - implementation(libs.kotlin) + implementation(libs.kotlinRuntime) + implementation(libs.kotlinReflect) implementation(libs.bundles.jUnit5) implementation(libs.bundles.testTools) implementation(libs.gson) From 0a9ae310e7c9fe89722befce968751b213849c3b Mon Sep 17 00:00:00 2001 From: Nikita Ogorodnikov Date: Mon, 12 Oct 2026 12:20:01 +0200 Subject: [PATCH 2/4] RUM-18937: Update Detekt to version 2.0.0-alpha.1 --- LICENSE-3rdparty.csv | 1 + .../main/kotlin/detekt-conventions.gradle.kts | 27 +-- .../test-pyramid-api-surface.gradle.kts | 29 +-- .../kotlin/test-pyramid-api-usage.gradle.kts | 34 ++- .../android/core/internal/DatadogCore.kt | 2 +- .../file/batch/PlainBatchFileReaderWriter.kt | 2 +- .../core/internal/NoOpInternalSdkCoreTest.kt | 9 +- .../android/core/internal/SdkFeatureTest.kt | 2 +- .../file/batch/BatchFileOrchestratorTest.kt | 4 +- .../system/DefaultAndroidInfoProviderTest.kt | 2 +- .../internal/collections/EvictingQueue.kt | 2 +- detekt.yml | 84 ++++---- detekt_custom_safe_calls_android.yml | 4 +- detekt_custom_safe_calls_third_party.yml | 27 +-- .../openfeature/DatadogFlagsProviderTest.kt | 1 - .../android/flags/_FlagsInternalProxy.kt | 2 +- .../kotlin/com/datadog/android/log/Logger.kt | 12 +- .../domain/event/LogEventSerializerTest.kt | 14 +- .../internal/net/LogsRequestFactoryTest.kt | 6 +- .../internal/ProfilingRequestFactoryTest.kt | 3 +- .../internal/quota/QuotaCheckerTest.kt | 3 + .../datadog/android/rum/GlobalRumMonitor.kt | 2 +- .../com/datadog/android/rum/RumMonitor.kt | 2 +- .../domain/scope/ExternalResourceTimings.kt | 2 +- .../internal/domain/scope/RumActionScope.kt | 4 +- .../internal/domain/scope/RumResourceScope.kt | 4 +- .../internal/domain/scope/RumSessionScope.kt | 2 +- .../domain/scope/RumViewEventWriter.kt | 1 - .../rum/internal/domain/scope/RumViewScope.kt | 12 +- .../rum/internal/monitor/DatadogRumMonitor.kt | 2 +- .../startup/RumSessionScopeStartupManager.kt | 6 +- .../collector/DefaultTimeseriesCollector.kt | 4 +- .../android/rum/internal/utils/SdkCoreExt.kt | 5 +- .../domain/event/RumEventSerializerTest.kt | 202 ++++++++---------- .../internal/domain/scope/RumViewScopeTest.kt | 1 + .../rum/internal/model/ViewEventDiffTest.kt | 4 +- .../rum/internal/net/RumRequestFactoryTest.kt | 7 +- .../rum/internal/utils/SdkCoreExtTest.kt | 13 +- .../semantics/ImageSemanticsNodeMapperTest.kt | 4 - .../internal/processor/MutationResolver.kt | 6 +- .../recorder/SessionReplayRecorder.kt | 1 + .../internal/recorder/SnapshotProducer.kt | 2 +- .../SessionReplayConfigurationBuilderTest.kt | 2 +- .../internal/gson/GsonExtTest.kt | 5 + .../recorder/WindowReflectionUtilsTest.kt | 2 +- .../internal/DatadogPropagationHelper.kt | 4 +- .../trace/internal/_TraceInternalProxy.kt | 2 +- .../domain/event/SpanEventSerializerTest.kt | 3 +- .../internal/net/TracesRequestFactoryTest.kt | 6 +- .../replay/WebViewReplayEventMapper.kt | 4 +- gradle/libs.versions.toml | 10 +- .../internal/InternalInteractionTracking.kt | 5 +- .../okhttp/trace/TracingInterceptor.kt | 4 +- .../trace/TracingInterceptorTestsExt.kt | 2 +- tools/detekt/build.gradle.kts | 12 +- .../datadog/tools/detekt/DatadogProvider.kt | 29 ++- .../detekt/DatadogTestPyramidProvider.kt | 13 +- .../com/datadog/tools/detekt/ext/KaTypeExt.kt | 73 +++++++ .../datadog/tools/detekt/ext/KotlinTypeExt.kt | 47 ---- .../datadog/tools/detekt/ext/ReceiverExt.kt | 173 +++++++++++---- .../rules/AbstractCallExpressionRule.kt | 159 +++++++++----- .../tools/detekt/rules/AbstractTypedRule.kt | 16 +- .../tools/detekt/rules/pyramid/ApiSurface.kt | 23 +- .../tools/detekt/rules/pyramid/ApiUsage.kt | 23 +- .../tools/detekt/rules/sdk/CheckInternal.kt | 26 +-- .../detekt/rules/sdk/InvalidStringFormat.kt | 186 ++++++++-------- .../detekt/rules/sdk/PackageNameVisibility.kt | 33 +-- .../detekt/rules/sdk/PreferTimeProvider.kt | 30 +-- .../tools/detekt/rules/sdk/RequireInternal.kt | 26 +-- .../tools/detekt/rules/sdk/ThreadSafety.kt | 119 ++++------- .../rules/sdk/ThrowingInternalException.kt | 26 +-- .../tools/detekt/rules/sdk/TodoWithoutTask.kt | 29 +-- .../rules/sdk/UnsafeCallOnNullableType.kt | 24 +-- .../rules/sdk/UnsafeThirdPartyFunctionCall.kt | 40 ++-- ...rovider => dev.detekt.api.RuleSetProvider} | 0 .../src/main/resources/config/config.yml | 48 +++++ .../detekt/rules/pyramid/ApiSurfaceTest.kt | 22 +- .../detekt/rules/sdk/CheckInternalTest.kt | 21 +- .../rules/sdk/InvalidStringFormatTest.kt | 155 +++++++------- .../rules/sdk/PackageNameVisibilityTest.kt | 18 +- .../rules/sdk/PreferTimeProviderTest.kt | 8 +- .../detekt/rules/sdk/RequireInternalTest.kt | 21 +- .../detekt/rules/sdk/ThreadSafetyTest.kt | 150 +++++++------ .../sdk/ThrowingInternalExceptionTest.kt | 13 +- .../detekt/rules/sdk/TodoWithoutTaskTest.kt | 8 +- .../rules/sdk/UnsafeCallOnNullableTypeTest.kt | 13 +- .../sdk/UnsafeThirdPartyFunctionCallTest.kt | 124 +++++++---- 87 files changed, 1175 insertions(+), 1138 deletions(-) create mode 100644 tools/detekt/src/main/kotlin/com/datadog/tools/detekt/ext/KaTypeExt.kt delete mode 100644 tools/detekt/src/main/kotlin/com/datadog/tools/detekt/ext/KotlinTypeExt.kt rename tools/detekt/src/main/resources/META-INF/services/{io.gitlab.arturbosch.detekt.api.RuleSetProvider => dev.detekt.api.RuleSetProvider} (100%) create mode 100644 tools/detekt/src/main/resources/config/config.yml diff --git a/LICENSE-3rdparty.csv b/LICENSE-3rdparty.csv index df8c3cc962..a11f76a9d0 100644 --- a/LICENSE-3rdparty.csv +++ b/LICENSE-3rdparty.csv @@ -209,6 +209,7 @@ build,commons-cli,Apache-2.0,Copyright 2002-2024 The Apache Software Foundation build,commons-codec,Apache-2.0,Copyright 2002-2024 The Apache Software Foundation build,commons-io,Apache-2.0,Copyright 2002-2024 The Apache Software Foundation build,commons-logging,Apache-2.0,Copyright 2002-2024 The Apache Software Foundation +build,dev.detekt,Apache-2.0,Copyright 2016-2017 Artur Bosch & Contributors build,io.github.aakira,Apache-2.0,"Copyright (C) 2019 A.Akira" build,io.gitlab.arturbosch.detekt,Apache-2.0,Copyright 2016-2019 the original author or authors build,io.github.classgraph,MIT,"Copyright (c) 2019 Luke Hutchison" diff --git a/build-logic/src/main/kotlin/detekt-conventions.gradle.kts b/build-logic/src/main/kotlin/detekt-conventions.gradle.kts index d1dd700b93..ec1778d39e 100644 --- a/build-logic/src/main/kotlin/detekt-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/detekt-conventions.gradle.kts @@ -1,5 +1,4 @@ -import com.android.build.api.variant.LibraryAndroidComponentsExtension -import io.gitlab.arturbosch.detekt.Detekt +import dev.detekt.gradle.Detekt /* * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. @@ -8,7 +7,7 @@ import io.gitlab.arturbosch.detekt.Detekt */ plugins { - id("io.gitlab.arturbosch.detekt") + id("dev.detekt") } dependencies { @@ -32,9 +31,8 @@ tasks.withType().configureEach { reports { sarif.required = false html.required = false - xml.required = false - txt.required = false - md.required = false + markdown.required = false + checkstyle.required = false } } @@ -48,20 +46,3 @@ tasks.detekt { tasks.named("detektTest") ) } - -// testFixtures is an Android source set and isn't picked up by the detekt/AGP on AGP 8, this should -// be able to be deleted in AGP 9 and covered by `detektTest` -extensions.getByType().onVariants { variant -> - if (variant.name == "debug") { - variant.testFixtures?.let { testFixtures -> - val detektTestFixtures = tasks.register("detektTestFixtures") { - source(layout.projectDirectory.dir("src/testFixtures/kotlin")) - config.setFrom(detektConfigFiles) - classpath.setFrom(testFixtures.compileClasspath) - } - tasks.detekt { - dependsOn(detektTestFixtures) - } - } - } -} diff --git a/build-logic/src/main/kotlin/test-pyramid-api-surface.gradle.kts b/build-logic/src/main/kotlin/test-pyramid-api-surface.gradle.kts index c90c2b561d..912c3c2e42 100644 --- a/build-logic/src/main/kotlin/test-pyramid-api-surface.gradle.kts +++ b/build-logic/src/main/kotlin/test-pyramid-api-surface.gradle.kts @@ -5,23 +5,19 @@ */ import com.android.build.api.variant.LibraryAndroidComponentsExtension -import io.gitlab.arturbosch.detekt.Detekt -import org.gradle.api.artifacts.VersionCatalogsExtension +import dev.detekt.gradle.Detekt import org.gradle.api.attributes.Usage import org.gradle.kotlin.dsl.getByType import org.gradle.kotlin.dsl.named -import org.gradle.kotlin.dsl.the -// Applying `io.gitlab.arturbosch.detekt` would also register its own detekt tasks and apply detekt.yml -// - unwanted, since this plugin isn't linting, only using Detekt as an engine. All our own task actually needs -// from the plugin is the engine classpath, so provide that directly via detached configurations instead of applying it. -val detektVersion = the().named("libs").findVersion("detekt").get().requiredVersion -val detektEngineClasspath = configurations.detachedConfiguration( - dependencies.create("io.gitlab.arturbosch.detekt:detekt-cli:$detektVersion") -) -val detektPluginClasspath = configurations.detachedConfiguration( - dependencies.create(project(":tools:detekt")) -) +plugins { + // Provides the Detekt engine classpath and the defaults of the `Detekt` tasks + id("dev.detekt") +} + +dependencies { + detektPlugins(project(":tools:detekt")) +} val apiSurfaceLogFile = layout.buildDirectory.file("reports/detekt-test-pyramid/apiSurface.log") val generatedConfigFile = layout.buildDirectory.file("tmp/detekt-test-pyramid/apiSurfaceTestPyramidConfig.yml") @@ -47,8 +43,6 @@ androidComponents.onVariants(androidComponents.selector().withBuildType("debug") ) } - detektClasspath.setFrom(detektEngineClasspath) - pluginClasspath.setFrom(detektPluginClasspath) classpath.setFrom(variant.compileClasspath, androidComponents.sdkComponents.bootClasspath) jvmTarget = "17" buildUponDefaultConfig = false @@ -58,9 +52,8 @@ androidComponents.onVariants(androidComponents.selector().withBuildType("debug") reports { sarif.required = false html.required = false - xml.required = false - txt.required = false - md.required = false + markdown.required = false + checkstyle.required = false } // Internal working state regenerated every run, not a published output - LocalState, not @OutputFile. diff --git a/build-logic/src/main/kotlin/test-pyramid-api-usage.gradle.kts b/build-logic/src/main/kotlin/test-pyramid-api-usage.gradle.kts index 25615c84c1..4504e17b79 100644 --- a/build-logic/src/main/kotlin/test-pyramid-api-usage.gradle.kts +++ b/build-logic/src/main/kotlin/test-pyramid-api-usage.gradle.kts @@ -8,25 +8,26 @@ import com.android.build.api.variant.ApplicationAndroidComponentsExtension import com.android.build.api.variant.HasUnitTest import com.android.build.api.variant.LibraryAndroidComponentsExtension import com.android.build.api.variant.Variant -import io.gitlab.arturbosch.detekt.Detekt -import org.gradle.api.artifacts.VersionCatalogsExtension +import dev.detekt.gradle.Detekt import org.gradle.api.attributes.Usage import org.gradle.api.file.RegularFile import org.gradle.api.provider.Provider import org.gradle.kotlin.dsl.findByType import org.gradle.kotlin.dsl.named -import org.gradle.kotlin.dsl.the -// Applying `io.gitlab.arturbosch.detekt` would also register its own detekt tasks and apply detekt.yml -// - unwanted, since this plugin isn't linting, only using Detekt as an engine. All our own task actually needs -// from the plugin is the engine classpath, so provide that directly via detached configurations instead of applying it. -val detektVersion = the().named("libs").findVersion("detekt").get().requiredVersion -val detektEngineClasspath = configurations.detachedConfiguration( - dependencies.create("io.gitlab.arturbosch.detekt:detekt-cli:$detektVersion") -) -val detektPluginClasspath = configurations.detachedConfiguration( - dependencies.create(project(":tools:detekt")) -) +plugins { + // Provides the Detekt engine classpath and the defaults of the `Detekt` tasks + id("dev.detekt") +} + +dependencies { + detektPlugins(project(":tools:detekt")) +} + +// This plugin only uses Detekt as an engine: these modules aren't linted, so keep the root `detekt` task away from them +tasks.detekt { + isEnabled = false +} val apiUsageLogFile = layout.buildDirectory.file("reports/detekt-test-pyramid/apiUsage.log") val generatedConfigFile = layout.buildDirectory.file("tmp/detekt-test-pyramid/apiUsageTestPyramidConfig.yml") @@ -41,8 +42,6 @@ fun configureApiUsageVariant(variant: Variant, bootClasspath: Provider ) { diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/persistence/file/batch/PlainBatchFileReaderWriter.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/persistence/file/batch/PlainBatchFileReaderWriter.kt index 21033bdf4c..f6e471f745 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/persistence/file/batch/PlainBatchFileReaderWriter.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/persistence/file/batch/PlainBatchFileReaderWriter.kt @@ -178,7 +178,7 @@ internal class PlainBatchFileReaderWriter( } @Throws(IOException::class) - @Suppress("UnsafeThirdPartyFunctionCall", "ComplexMethod", "LoopWithTooManyJumpStatements") + @Suppress("UnsafeThirdPartyFunctionCall", "CyclomaticComplexMethod", "LoopWithTooManyJumpStatements") // Called within a try/catch block private fun readFileData( file: File, diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/NoOpInternalSdkCoreTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/NoOpInternalSdkCoreTest.kt index 9df8a431f2..55756d28ab 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/NoOpInternalSdkCoreTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/NoOpInternalSdkCoreTest.kt @@ -28,6 +28,7 @@ import org.mockito.junit.jupiter.MockitoSettings import org.mockito.kotlin.doReturn import org.mockito.kotlin.whenever import org.mockito.quality.Strictness +import java.util.Locale @Extensions( ExtendWith(MockitoExtension::class), @@ -92,7 +93,7 @@ internal class NoOpInternalSdkCoreTest { // Given whenever(mockFeature.name) doReturn fakeFeatureName val expectedMessage = "[${NoOpInternalSdkCore.name}]: " + - REGISTER_FEATURE_INVOKED_ON_NO_OP_CORE_ERROR.format(fakeFeatureName) + REGISTER_FEATURE_INVOKED_ON_NO_OP_CORE_ERROR.format(Locale.US, fakeFeatureName) // When + Then Mockito.mockStatic(Log::class.java).use { mockedLog -> @@ -111,7 +112,7 @@ internal class NoOpInternalSdkCoreTest { // Given whenever(mockFeature.name) doReturn fakeFeatureName val expectedMessage = "[${NoOpInternalSdkCore.name}]: " + - REGISTER_FEATURE_INVOKED_ON_NO_OP_CORE_ERROR.format(fakeFeatureName) + REGISTER_FEATURE_INVOKED_ON_NO_OP_CORE_ERROR.format(Locale.US, fakeFeatureName) // When + Then Mockito.mockStatic(Log::class.java).use { mockedLog -> @@ -128,7 +129,7 @@ internal class NoOpInternalSdkCoreTest { fun `M log error W getFeature()`(@StringForgery fakeFeatureName: String) { // Given val expectedMessage = "[${NoOpInternalSdkCore.name}]: " + - GET_FEATURE_INVOKED_ON_NO_OP_CORE_ERROR.format(fakeFeatureName) + GET_FEATURE_INVOKED_ON_NO_OP_CORE_ERROR.format(Locale.US, fakeFeatureName) // When + Then Mockito.mockStatic(Log::class.java).use { mockedLog -> @@ -144,7 +145,7 @@ internal class NoOpInternalSdkCoreTest { fun `M log error only once W getFeature() {called multiple times}`(@StringForgery fakeFeatureName: String) { // Given val expectedMessage = "[${NoOpInternalSdkCore.name}]: " + - GET_FEATURE_INVOKED_ON_NO_OP_CORE_ERROR.format(fakeFeatureName) + GET_FEATURE_INVOKED_ON_NO_OP_CORE_ERROR.format(Locale.US, fakeFeatureName) // When + Then Mockito.mockStatic(Log::class.java).use { mockedLog -> diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/SdkFeatureTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/SdkFeatureTest.kt index 6c4db3887f..48918200ad 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/SdkFeatureTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/SdkFeatureTest.kt @@ -755,7 +755,7 @@ internal class SdkFeatureTest { assertThrows { // strange enough nothing is thrown if we don't save the result. // Kotlin compiler removing/optimizing code unused? - @Suppress("UNUSED_VARIABLE") + @Suppress("UnusedVariable", "unused") val result = testedFeature.unwrap() } } diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/batch/BatchFileOrchestratorTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/batch/BatchFileOrchestratorTest.kt index 62fcac733a..ff2ebe02b8 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/batch/BatchFileOrchestratorTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/persistence/file/batch/BatchFileOrchestratorTest.kt @@ -603,7 +603,7 @@ internal class BatchFileOrchestratorTest { forge.anAlphabeticalString() } - previousFile?.writeText(previousData[0]) + previousFile.writeText(previousData[0]) for (i in 1 until MAX_ITEM_PER_BATCH) { val file = testedOrchestrator.getWritableFile(1L) @@ -623,7 +623,7 @@ internal class BatchFileOrchestratorTest { .doesNotExist() .hasParent(fakeRootDir) assertThat(nextFile.name.toLong()).isEqualTo(newFileTimestamp) - assertThat(previousFile?.readText()) + assertThat(previousFile.readText()) .isEqualTo(previousData.joinToString(separator = "")) argumentCaptor { diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/system/DefaultAndroidInfoProviderTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/system/DefaultAndroidInfoProviderTest.kt index 9723575cea..13e2304483 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/system/DefaultAndroidInfoProviderTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/system/DefaultAndroidInfoProviderTest.kt @@ -486,7 +486,7 @@ internal class DefaultAndroidInfoProviderTest { val fakeLocales = mutableListOf() val mockLocaleList: LocaleList = mock() - for (i in 0 until collectionSize) { + repeat(collectionSize) { val fakeLanguage = forge.aStringMatching("[a-z][a-z]{1}") val fakeRegion = forge.aStringMatching("[a-z][a-z]{1}") fakeLocales.add( diff --git a/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/collections/EvictingQueue.kt b/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/collections/EvictingQueue.kt index cb80ce523b..ddfd749adf 100644 --- a/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/collections/EvictingQueue.kt +++ b/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/collections/EvictingQueue.kt @@ -99,7 +99,7 @@ class EvictingQueue constructor( else -> { val spaceLeft = maxSize - size - for (index in 0 until elements.size - spaceLeft) { + repeat(elements.size - spaceLeft) { delegate.poll() } diff --git a/detekt.yml b/detekt.yml index b80ca52eac..3e9299fdbd 100644 --- a/detekt.yml +++ b/detekt.yml @@ -1,13 +1,9 @@ -build: - maxIssues: 0 - excludeCorrectable: false - config: validation: true warningsAsErrors: true checkExhaustiveness: false # when writing own rules with new properties, exclude the property path e.g.: 'my_rule_set,.*>.*>[my_property]' - excludes: '' + excludes: [] processors: active: true @@ -16,11 +12,7 @@ console-reports: active: true exclude: - 'ComplexityReport' - - 'FileBasedFindingsReport' - - 'BuildFailureReport' - -output-reports: - active: false + - 'FileBasedIssuesReport' comments: active: true @@ -113,20 +105,20 @@ complexity: excludes: ['**/build/**'] CognitiveComplexMethod: active: false - threshold: 15 + allowedComplexity: 15 ComplexCondition: active: true - threshold: 4 + allowedConditions: 3 ComplexInterface: active: false - threshold: 10 + allowedDefinitions: 10 includeStaticDeclarations: false includePrivateDeclarations: false ignoreOverloaded: false CyclomaticComplexMethod: active: true excludes: ['**/test/**', '**/*Test/**', '**/testFixtures/**'] - threshold: 15 + allowedComplexity: 14 ignoreSingleWhenExpression: true ignoreSimpleWhenEntries: true ignoreNestingFunctions: false @@ -146,33 +138,33 @@ complexity: LargeClass: active: true excludes: ['**/test/**', '**/*Test/**', '**/testFixtures/**'] - threshold: 600 + allowedLines: 600 LongMethod: active: true excludes: ['**/test/**', '**/*Test/**', '**/testFixtures/**'] - threshold: 60 + allowedLines: 60 LongParameterList: active: true excludes: ['**/test/**', '**/*Test/**', '**/testFixtures/**'] - functionThreshold: 12 - constructorThreshold: 12 + allowedFunctionParameters: 11 + allowedConstructorParameters: 11 ignoreDefaultParameters: true ignoreDataClasses: true ignoreAnnotatedParameter: [] ignoreAnnotated: [ 'Composable' ] MethodOverloading: active: true - threshold: 6 + allowedOverloads: 6 NamedArguments: active: false - threshold: 6 + allowedArguments: 6 ignoreArgumentsMatchingNames: true NestedBlockDepth: active: true - threshold: 4 + allowedDepth: 4 NestedScopeFunctions: active: false - threshold: 1 + allowedDepth: 1 functions: - 'kotlin.apply' - 'kotlin.run' @@ -184,18 +176,18 @@ complexity: StringLiteralDuplication: active: true excludes: ['**/test/**', '**/*Test/**', '**/testFixtures/**'] - threshold: 3 + allowedDuplications: 2 ignoreAnnotation: true - excludeStringsWithLessThan5Characters: true + allowedWithLengthLessThan: 5 ignoreStringsRegex: '$^' TooManyFunctions: active: true excludes: ['**/test/**', '**/*Test/**', '**/testFixtures/**'] - thresholdInFiles: 11 - thresholdInClasses: 11 - thresholdInInterfaces: 11 - thresholdInObjects: 11 - thresholdInEnums: 11 + allowedFunctionsPerFile: 11 + allowedFunctionsPerClass: 11 + allowedFunctionsPerInterface: 11 + allowedFunctionsPerObject: 11 + allowedFunctionsPerEnum: 11 ignoreDeprecated: true ignorePrivate: false ignoreOverridden: true @@ -248,7 +240,7 @@ empty-blocks: active: true EmptyInitBlock: active: true - EmptyKtFile: + EmptyKotlinFile: active: true EmptySecondaryConstructor: active: true @@ -356,11 +348,11 @@ naming: active: true excludes: ['**/test/**', '**/*Test/**', '**/testFixtures/**'] forbiddenName: [] - FunctionMaxLength: + FunctionNameMaxLength: active: true excludes: ['**/test/**', '**/*Test/**', '**/testFixtures/**'] maximumFunctionNameLength: 100 - FunctionMinLength: + FunctionNameMinLength: active: true excludes: ['**/test/**', '**/*Test/**', '**/testFixtures/**'] minimumFunctionNameLength: 2 @@ -428,7 +420,7 @@ performance: active: true CouldBeSequence: active: true - threshold: 3 + allowedOperations: 3 ForEachOnRange: active: true excludes: ['**/test/**', '**/*Test/**', '**/testFixtures/**'] @@ -597,6 +589,8 @@ style: active: false ExplicitItLambdaParameter: active: true + ExplicitItLambdaMultipleParameters: + active: true ExpressionBodySyntax: active: false includeLineWrapping: false @@ -683,7 +677,7 @@ style: excludeImportStatements: true excludeCommentStatements: true excludeRawStrings: true - MayBeConst: + MayBeConstant: active: true ModifierOrder: active: true @@ -709,15 +703,13 @@ style: active: true OptionalUnit: active: false - PreferToOverPairSyntax: - active: false ProtectedMemberInFinalClass: active: true RedundantExplicitType: active: false RedundantHigherOrderMapUsage: active: true - RedundantVisibilityModifierRule: + RedundantVisibilityModifier: active: false ReturnCount: active: true @@ -731,8 +723,6 @@ style: active: true SerialVersionUIDInSerializableClass: active: true - SpacingBetweenPackageAndImports: - active: false StringShouldBeRawString: active: false maxEscapedCharacterCount: 2 @@ -753,7 +743,10 @@ style: active: false acceptableLength: 4 allowNonStandardGrouping: false - UnnecessaryAbstractClass: + AbstractClassCanBeConcreteClass: + active: false + excludes: ['**/test/**', '**/*Test/**', '**/testFixtures/**', '**/tools/**'] + AbstractClassCanBeInterface: active: false excludes: ['**/test/**', '**/*Test/**', '**/testFixtures/**', '**/tools/**'] UnnecessaryAnnotationUseSiteTarget: @@ -775,16 +768,17 @@ style: UnnecessaryParentheses: active: false allowForUnclearPrecedence: false - UntilInsteadOfRangeTo: + RangeUntilInsteadOfRangeTo: active: false - UnusedImports: + UnusedImport: active: true UnusedParameter: active: true allowedNames: 'ignored|expected' UnusedPrivateClass: active: true - UnusedPrivateMember: + aliases: ['unused'] + UnusedPrivateFunction: active: true allowedNames: '' ignoreAnnotated: @@ -792,6 +786,9 @@ style: UnusedPrivateProperty: active: true allowedNames: '_|ignored|expected|serialVersionUID' + UnusedVariable: + active: true + allowedNames: 'ignored|_' UseAnyOrNoneInsteadOfFind: active: true UseArrayLiteralsInAnnotations: @@ -879,7 +876,6 @@ datadog: active: true internalPackagePrefix: 'com.datadog' treatUnknownFunctionAsThrowing: true - treatUnknownConstructorAsThrowing: true datadog-test-pyramid: active: false diff --git a/detekt_custom_safe_calls_android.yml b/detekt_custom_safe_calls_android.yml index 4835caa866..29d8b4518f 100644 --- a/detekt_custom_safe_calls_android.yml +++ b/detekt_custom_safe_calls_android.yml @@ -113,7 +113,6 @@ datadog: - "android.view.FrameMetrics.getMetric(kotlin.Int)" - "android.view.GestureDetector.constructor(android.content.Context?, android.view.GestureDetector.OnGestureListener)" - "android.view.GestureDetector.onTouchEvent(android.view.MotionEvent)" - - "android.view.getDefaultSize(kotlin.Int, kotlin.Int)" - "android.view.LayoutInflater.inflate(kotlin.Int, android.view.ViewGroup?, kotlin.Boolean)" - "android.view.MotionEvent.PointerCoords.constructor()" - "android.view.MotionEvent.getPointerCoords(kotlin.Int, android.view.MotionEvent.PointerCoords)" @@ -121,12 +120,12 @@ datadog: - "android.view.MotionEvent.getRawX(kotlin.Int)" - "android.view.MotionEvent.getRawY(kotlin.Int)" - "android.view.MotionEvent.recycle()" - - "android.view.resolveSize(kotlin.Int, kotlin.Int)" - "android.view.View.addOnAttachStateChangeListener(android.view.View.OnAttachStateChangeListener?)" - "android.view.View.animate()" - "android.view.View.bringToFront()" - "android.view.View.findViewById(kotlin.Int)" - "android.view.View.getChildAt(kotlin.Int)" + - "android.view.View.getDefaultSize(kotlin.Int, kotlin.Int)" - "android.view.View.getTag(kotlin.Int)" - "android.view.View.hashCode()" - "android.view.View.onDraw(android.graphics.Canvas)" @@ -134,6 +133,7 @@ datadog: - "android.view.View.postInvalidateOnAnimation()" - "android.view.View.post(java.lang.Runnable?)" - "android.view.View.removeOnAttachStateChangeListener(android.view.View.OnAttachStateChangeListener?)" + - "android.view.View.resolveSize(kotlin.Int, kotlin.Int)" - "android.view.View.setOnClickListener(android.view.View.OnClickListener?)" - "android.view.View.setOnTouchListener(android.view.View.OnTouchListener?)" - "android.view.View.setTag(kotlin.Int, kotlin.Any?)" diff --git a/detekt_custom_safe_calls_third_party.yml b/detekt_custom_safe_calls_third_party.yml index cc6cabd804..5d2ee59bc0 100644 --- a/detekt_custom_safe_calls_third_party.yml +++ b/detekt_custom_safe_calls_third_party.yml @@ -75,8 +75,8 @@ datadog: - "com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader.Factory.constructor(okhttp3.Call.Factory)" - "com.bumptech.glide.load.engine.executor.GlideExecutor.Builder.build()" - "com.bumptech.glide.load.engine.executor.GlideExecutor.Builder.setUncaughtThrowableStrategy(com.bumptech.glide.load.engine.executor.GlideExecutor.UncaughtThrowableStrategy)" - - "com.bumptech.glide.load.engine.executor.newDiskCacheBuilder()" - - "com.bumptech.glide.load.engine.executor.newSourceBuilder()" + - "com.bumptech.glide.load.engine.executor.GlideExecutor.newDiskCacheBuilder()" + - "com.bumptech.glide.load.engine.executor.GlideExecutor.newSourceBuilder()" # endregion # region Gson - "com.google.gson.JsonArray.add(com.google.gson.JsonElement?)" @@ -234,15 +234,8 @@ datadog: - "java.util.concurrent.ThreadPoolExecutor.newTaskFor(java.lang.Runnable?, kotlin.Any?)" - "java.util.concurrent.ThreadPoolExecutor.newTaskFor(java.util.concurrent.Callable?)" - "java.util.concurrent.ThreadPoolExecutor.shutdownNow()" - - "java.util.concurrent.TimeUnit.HOURS.toMillis(kotlin.Long)" - - "java.util.concurrent.TimeUnit.HOURS.toNanos(kotlin.Long)" - - "java.util.concurrent.TimeUnit.MICROSECONDS.toMillis(kotlin.Long)" - - "java.util.concurrent.TimeUnit.MILLISECONDS.toNanos(kotlin.Long)" - - "java.util.concurrent.TimeUnit.MINUTES.toMillis(kotlin.Long)" - - "java.util.concurrent.TimeUnit.MINUTES.toNanos(kotlin.Long)" - - "java.util.concurrent.TimeUnit.NANOSECONDS.toMillis(kotlin.Long)" - - "java.util.concurrent.TimeUnit.SECONDS.toMillis(kotlin.Long)" - - "java.util.concurrent.TimeUnit.SECONDS.toNanos(kotlin.Long)" + - "java.util.concurrent.TimeUnit.toMillis(kotlin.Long)" + - "java.util.concurrent.TimeUnit.toNanos(kotlin.Long)" - "java.util.concurrent.atomic.AtomicBoolean.compareAndSet(kotlin.Boolean, kotlin.Boolean)" - "java.util.concurrent.atomic.AtomicBoolean.constructor(kotlin.Boolean)" - "java.util.concurrent.atomic.AtomicBoolean.get()" @@ -362,9 +355,9 @@ datadog: - "java.lang.ref.WeakReference.constructor(kotlin.Nothing?)" - "java.lang.ref.WeakReference.constructor(kotlin.String?)" - "java.lang.ref.WeakReference.get()" - - "java.lang.reflect.isStatic(kotlin.Int)" - "java.lang.reflect.Field.getSafe(kotlin.Any?)" - "java.lang.reflect.Field.accessible()" + - "java.lang.reflect.Modifier.isStatic(kotlin.Int)" - "java.lang.StringBuilder.append(kotlin.Char)" - "java.lang.StringBuilder.append(kotlin.CharArray?)" - "java.lang.StringBuilder.append(kotlin.String?)" @@ -1212,11 +1205,11 @@ datadog: - "io.opentelemetry.context.Scope.close()" # endregion # region RxJava - - "io.reactivex.rxjava3.core.Completable.doOnError(io.reactivex.rxjava3.functions.Consumer?)" - - "io.reactivex.rxjava3.core.Flowable.doOnError(io.reactivex.rxjava3.functions.Consumer?)" - - "io.reactivex.rxjava3.core.Maybe.doOnError(io.reactivex.rxjava3.functions.Consumer?)" - - "io.reactivex.rxjava3.core.Observable.doOnError(io.reactivex.rxjava3.functions.Consumer?)" - - "io.reactivex.rxjava3.core.Single.doOnError(io.reactivex.rxjava3.functions.Consumer?)" + - "io.reactivex.rxjava3.core.Completable.doOnError(io.reactivex.rxjava3.functions.Consumer)" + - "io.reactivex.rxjava3.core.Flowable.doOnError(io.reactivex.rxjava3.functions.Consumer)" + - "io.reactivex.rxjava3.core.Maybe.doOnError(io.reactivex.rxjava3.functions.Consumer)" + - "io.reactivex.rxjava3.core.Observable.doOnError(io.reactivex.rxjava3.functions.Consumer)" + - "io.reactivex.rxjava3.core.Single.doOnError(io.reactivex.rxjava3.functions.Consumer)" # endregion # region SQLDelight - "com.squareup.sqldelight.android.AndroidSqliteDriver.Callback.onCorruption(androidx.sqlite.db.SupportSQLiteDatabase)" diff --git a/features/dd-sdk-android-flags-openfeature/src/test/kotlin/com/datadog/android/flags/openfeature/DatadogFlagsProviderTest.kt b/features/dd-sdk-android-flags-openfeature/src/test/kotlin/com/datadog/android/flags/openfeature/DatadogFlagsProviderTest.kt index d8291bded4..1ba966ee90 100644 --- a/features/dd-sdk-android-flags-openfeature/src/test/kotlin/com/datadog/android/flags/openfeature/DatadogFlagsProviderTest.kt +++ b/features/dd-sdk-android-flags-openfeature/src/test/kotlin/com/datadog/android/flags/openfeature/DatadogFlagsProviderTest.kt @@ -50,7 +50,6 @@ import org.mockito.junit.jupiter.MockitoSettings import org.mockito.kotlin.any import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.doAnswer -import org.mockito.kotlin.doNothing import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.verify diff --git a/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/_FlagsInternalProxy.kt b/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/_FlagsInternalProxy.kt index 5b232f5298..7175b0616d 100644 --- a/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/_FlagsInternalProxy.kt +++ b/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/_FlagsInternalProxy.kt @@ -22,7 +22,7 @@ import com.datadog.android.lint.InternalApi * are not considered part of the public interface of the Datadog SDK. */ @InternalApi -@Suppress("ClassName", "UndocumentedPublicFunction") +@Suppress("ClassName", "ClassNaming", "UndocumentedPublicFunction") class _FlagsInternalProxy(private val client: FlagsClient) { fun getFlagAssignmentsSnapshot(): Map = if (client is DatadogFlagsClient) { client.getFlagAssignmentsSnapshot() diff --git a/features/dd-sdk-android-logs/src/main/kotlin/com/datadog/android/log/Logger.kt b/features/dd-sdk-android-logs/src/main/kotlin/com/datadog/android/log/Logger.kt index f254df5a00..e46df302ee 100644 --- a/features/dd-sdk-android-logs/src/main/kotlin/com/datadog/android/log/Logger.kt +++ b/features/dd-sdk-android-logs/src/main/kotlin/com/datadog/android/log/Logger.kt @@ -49,7 +49,7 @@ internal constructor(internal var handler: LogHandler) { * @param attributes a map of attributes to include only for this message. If an attribute with * the same key already exist in this logger, it will be overridden (just for this message) */ - @Suppress("FunctionMinLength") + @Suppress("FunctionNameMinLength") @JvmOverloads fun v( message: String, @@ -66,7 +66,7 @@ internal constructor(internal var handler: LogHandler) { * @param attributes a map of attributes to include only for this message. If an attribute with * the same key already exist in this logger, it will be overridden (just for this message) */ - @Suppress("FunctionMinLength") + @Suppress("FunctionNameMinLength") @JvmOverloads fun d( message: String, @@ -83,7 +83,7 @@ internal constructor(internal var handler: LogHandler) { * @param attributes a map of attributes to include only for this message. If an attribute with * the same key already exist in this logger, it will be overridden (just for this message) */ - @Suppress("FunctionMinLength") + @Suppress("FunctionNameMinLength") @JvmOverloads fun i( message: String, @@ -100,7 +100,7 @@ internal constructor(internal var handler: LogHandler) { * @param attributes a map of attributes to include only for this message. If an attribute with * the same key already exist in this logger, it will be overridden (just for this message) */ - @Suppress("FunctionMinLength") + @Suppress("FunctionNameMinLength") @JvmOverloads fun w( message: String, @@ -117,7 +117,7 @@ internal constructor(internal var handler: LogHandler) { * @param attributes a map of attributes to include only for this message. If an attribute with * the same key already exist in this logger, it will be overridden (just for this message) */ - @Suppress("FunctionMinLength") + @Suppress("FunctionNameMinLength") @JvmOverloads fun e( message: String, @@ -134,7 +134,7 @@ internal constructor(internal var handler: LogHandler) { * @param attributes a map of attributes to include only for this message. If an attribute with * the same key already exist in this logger, it will be overridden (just for this message) */ - @Suppress("FunctionMinLength") + @Suppress("FunctionNameMinLength") @JvmOverloads fun wtf( message: String, diff --git a/features/dd-sdk-android-logs/src/test/kotlin/com/datadog/android/log/internal/domain/event/LogEventSerializerTest.kt b/features/dd-sdk-android-logs/src/test/kotlin/com/datadog/android/log/internal/domain/event/LogEventSerializerTest.kt index b7b8a9c402..ad52984b00 100644 --- a/features/dd-sdk-android-logs/src/test/kotlin/com/datadog/android/log/internal/domain/event/LogEventSerializerTest.kt +++ b/features/dd-sdk-android-logs/src/test/kotlin/com/datadog/android/log/internal/domain/event/LogEventSerializerTest.kt @@ -169,10 +169,9 @@ internal class LogEventSerializerTest { } val faultyLogEvent = fakeLog.copy( usr = fakeLog.usr?.copy( - additionalProperties = fakeLog.usr?.additionalProperties - ?.toMutableMap() - ?.apply { put(faultyKey, faultyObject) } - .orEmpty() + additionalProperties = fakeLog.usr.additionalProperties + .toMutableMap() + .apply { put(faultyKey, faultyObject) } .toMutableMap() ) ) @@ -198,10 +197,9 @@ internal class LogEventSerializerTest { } val faultyLogEvent = fakeLog.copy( account = fakeLog.account?.copy( - additionalProperties = fakeLog.account?.additionalProperties - ?.toMutableMap() - ?.apply { put(faultyKey, faultyObject) } - .orEmpty() + additionalProperties = fakeLog.account.additionalProperties + .toMutableMap() + .apply { put(faultyKey, faultyObject) } .toMutableMap() ) ) diff --git a/features/dd-sdk-android-logs/src/test/kotlin/com/datadog/android/log/internal/net/LogsRequestFactoryTest.kt b/features/dd-sdk-android-logs/src/test/kotlin/com/datadog/android/log/internal/net/LogsRequestFactoryTest.kt index b9660fb137..c4010a0384 100644 --- a/features/dd-sdk-android-logs/src/test/kotlin/com/datadog/android/log/internal/net/LogsRequestFactoryTest.kt +++ b/features/dd-sdk-android-logs/src/test/kotlin/com/datadog/android/log/internal/net/LogsRequestFactoryTest.kt @@ -53,11 +53,10 @@ internal class LogsRequestFactoryTest { fun `M create a proper request W create()`( @Forgery batchData: List, @Forgery executionContext: RequestExecutionContext, - @StringForgery batchMetadata: String, forge: Forge ) { // Given - val batchMetadata = forge.aNullable { batchMetadata.toByteArray() } + val batchMetadata = forge.aNullable { forge.aString().toByteArray() } // When val request = testedFactory.create(fakeDatadogContext, executionContext, batchData, batchMetadata) @@ -95,7 +94,6 @@ internal class LogsRequestFactoryTest { fun `M create a proper request W create() { custom endpoint }`( @StringForgery(regex = "https://[a-z]+\\.com(/[a-z]+)+") fakeEndpoint: String, @Forgery batchData: List, - @StringForgery batchMetadata: String, @Forgery executionContext: RequestExecutionContext, forge: Forge ) { @@ -104,7 +102,7 @@ internal class LogsRequestFactoryTest { customEndpointUrl = fakeEndpoint, internalLogger = InternalLogger.UNBOUND ) - val batchMetadata = forge.aNullable { batchMetadata.toByteArray() } + val batchMetadata = forge.aNullable { forge.aString().toByteArray() } // When val request = testedFactory.create(fakeDatadogContext, executionContext, batchData, batchMetadata) diff --git a/features/dd-sdk-android-profiling/src/test/kotlin/com/datadog/android/profiling/internal/ProfilingRequestFactoryTest.kt b/features/dd-sdk-android-profiling/src/test/kotlin/com/datadog/android/profiling/internal/ProfilingRequestFactoryTest.kt index 2c9a4c7ead..e813203bd8 100644 --- a/features/dd-sdk-android-profiling/src/test/kotlin/com/datadog/android/profiling/internal/ProfilingRequestFactoryTest.kt +++ b/features/dd-sdk-android-profiling/src/test/kotlin/com/datadog/android/profiling/internal/ProfilingRequestFactoryTest.kt @@ -32,6 +32,7 @@ import org.mockito.kotlin.eq import org.mockito.kotlin.isNull import org.mockito.kotlin.verify import org.mockito.quality.Strictness +import java.util.Locale @Extensions( ExtendWith(MockitoExtension::class), @@ -212,7 +213,7 @@ class ProfilingRequestFactoryTest { eq(null) ) assertThat(firstValue()).isEqualTo( - ProfilingRequestFactory.MULTIPLE_BATCH_EVENTS_WARNING_MESSAGE.format(batchData.size) + ProfilingRequestFactory.MULTIPLE_BATCH_EVENTS_WARNING_MESSAGE.format(Locale.US, batchData.size) ) } assertThat(request.body.containsSubsequence(fakeEvent.data)).isTrue() diff --git a/features/dd-sdk-android-profiling/src/test/kotlin/com/datadog/android/profiling/internal/quota/QuotaCheckerTest.kt b/features/dd-sdk-android-profiling/src/test/kotlin/com/datadog/android/profiling/internal/quota/QuotaCheckerTest.kt index 70a12a9ef3..dfa4449f02 100644 --- a/features/dd-sdk-android-profiling/src/test/kotlin/com/datadog/android/profiling/internal/quota/QuotaCheckerTest.kt +++ b/features/dd-sdk-android-profiling/src/test/kotlin/com/datadog/android/profiling/internal/quota/QuotaCheckerTest.kt @@ -38,6 +38,7 @@ import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import org.mockito.quality.Strictness import java.io.IOException +import java.util.Locale @Extensions( ExtendWith(MockitoExtension::class), @@ -218,6 +219,7 @@ internal class QuotaCheckerTest { // Then val capturedRequest = requestCaptor.firstValue val expectedUrl = ProfilingQuotaChecker.QUOTA_URL_TEMPLATE.format( + Locale.US, fakeDatadogContext.site.intakeEndpoint.removePrefix("https://"), fakeSessionId ) @@ -435,6 +437,7 @@ internal class QuotaCheckerTest { Request.Builder() .url( ProfilingQuotaChecker.QUOTA_URL_TEMPLATE.format( + Locale.US, fakeDatadogContext.site.intakeEndpoint.removePrefix("https://"), fakeSessionId ) diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/GlobalRumMonitor.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/GlobalRumMonitor.kt index 226c2e1c91..d717530ddc 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/GlobalRumMonitor.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/GlobalRumMonitor.kt @@ -118,7 +118,7 @@ object GlobalRumMonitor { } // This method is mainly for test purposes. - @Suppress("unused") + @Suppress("UnusedPrivateFunction", "unused") @JvmStatic private fun reset() { clear() diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/RumMonitor.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/RumMonitor.kt index bf08174e62..175bd12dfa 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/RumMonitor.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/RumMonitor.kt @@ -456,7 +456,7 @@ interface RumMonitor { * * @see _RumInternalProxy */ - @Suppress("FunctionName") + @Suppress("FunctionName", "FunctionNaming") @JvmSynthetic fun _getInternal(): _RumInternalProxy? } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/ExternalResourceTimings.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/ExternalResourceTimings.kt index a648da5052..3f2d2e39bb 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/ExternalResourceTimings.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/ExternalResourceTimings.kt @@ -41,7 +41,7 @@ internal fun extractResourceTiming(timingsPayload: Map?): Resource } } -@Suppress("ComplexMethod") +@Suppress("CyclomaticComplexMethod") private fun createResourceTiming(timings: Map): ResourceTiming { return ResourceTiming( firstByteStart = timings[FIRST_BYTE_TIMING]?.startTime ?: 0L, diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumActionScope.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumActionScope.kt index d71d183db6..28d7cf567d 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumActionScope.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumActionScope.kt @@ -228,7 +228,7 @@ internal class RumActionScope( longTaskCount++ } - @Suppress("LongMethod", "ComplexMethod") + @Suppress("LongMethod", "CyclomaticComplexMethod") private fun sendAction( endNanos: Long, datadogContext: DatadogContext, @@ -273,7 +273,7 @@ internal class RumActionScope( frustrations.add(ActionEvent.Type.ERROR_TAP) } - sdkCore.newRumEventWriteOperation(datadogContext, writeScope, writer) { + sdkCore.newRumEventWriteOperation(writeScope, writer) { val user = datadogContext.userInfo val hasReplay = featuresContextResolver.resolveViewHasReplay( datadogContext, diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumResourceScope.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumResourceScope.kt index d7e968f25a..beac1f1e93 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumResourceScope.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumResourceScope.kt @@ -287,7 +287,7 @@ internal class RumResourceScope( ResourceEvent.Response(headers = ResourceEvent.RequestHeaders(it.toMutableMap())) } - sdkCore.newRumEventWriteOperation(datadogContext, writeScope, writer) { + sdkCore.newRumEventWriteOperation(writeScope, writer) { val user = datadogContext.userInfo val hasReplay = featuresContextResolver.resolveViewHasReplay( datadogContext, @@ -458,7 +458,7 @@ internal class RumResourceScope( else -> ErrorEvent.ErrorEventSessionType.SYNTHETICS } - sdkCore.newRumEventWriteOperation(datadogContext, writeScope, writer) { + sdkCore.newRumEventWriteOperation(writeScope, writer) { val user = datadogContext.userInfo val hasReplay = featuresContextResolver.resolveViewHasReplay( datadogContext, diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt index 183b31c195..1925a16077 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt @@ -264,7 +264,7 @@ internal class RumSessionScope( return !isActive && childScope == null } - @Suppress("ComplexMethod") + @Suppress("CyclomaticComplexMethod") private fun updateSession(event: RumRawEvent) { val nanoTime = sdkCore.timeProvider.getDeviceElapsedRealtimeNanos() val isNewSession = sessionId == RumContext.NULL_UUID diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewEventWriter.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewEventWriter.kt index e89ceca4d4..037242f2ba 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewEventWriter.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewEventWriter.kt @@ -74,7 +74,6 @@ internal class RumViewEventWriterImpl( var mappedViewEvent: ViewEvent? = null sdkCore.newRumEventWriteOperation( - datadogContext = datadogContext, writeScope = writeScope, rumDataWriter = writer, eventType = eventType, diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScope.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScope.kt index 96a2e1ff27..d04df7f852 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScope.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScope.kt @@ -278,7 +278,7 @@ internal open class RumViewScope( val vitalId = UUID.randomUUID().toString() val capturedRumContext = getRumContext() - sdkCore.newRumEventWriteOperation(datadogContext, writeScope, writer) { + sdkCore.newRumEventWriteOperation(writeScope, writer) { newVitalEvent( vitalId, event, @@ -311,7 +311,7 @@ internal open class RumViewScope( val vitalId = UUID.randomUUID().toString() val capturedRumContext = getRumContext() - sdkCore.newRumEventWriteOperation(datadogContext, writeScope, writer) { + sdkCore.newRumEventWriteOperation(writeScope, writer) { newVitalEvent( vitalId, event, @@ -734,7 +734,7 @@ internal open class RumViewScope( pendingResourceCount++ } - @Suppress("ComplexMethod", "LongMethod") + @Suppress("CyclomaticComplexMethod", "LongMethod") @WorkerThread private fun onAddError( event: RumRawEvent.AddError, @@ -794,7 +794,7 @@ internal open class RumViewScope( } // end region - sdkCore.newRumEventWriteOperation(datadogContext, writeScope, writer, eventType) { + sdkCore.newRumEventWriteOperation(writeScope, writer, eventType) { val user = datadogContext.userInfo val syntheticsAttribute = if ( rumContext.syntheticsTestId.isNullOrBlank() || @@ -1233,7 +1233,7 @@ internal open class RumViewScope( } } - @Suppress("LongMethod", "ComplexMethod") + @Suppress("LongMethod", "CyclomaticComplexMethod") internal fun sendViewUpdate( event: RumRawEvent, datadogContext: DatadogContext, @@ -1571,7 +1571,7 @@ internal open class RumViewScope( ) val longTaskId = UUID.randomUUID().toString() - sdkCore.newRumEventWriteOperation(datadogContext, writeScope, writer) { + sdkCore.newRumEventWriteOperation(writeScope, writer) { val user = datadogContext.userInfo val syntheticsAttribute = if ( rumContext.syntheticsTestId.isNullOrBlank() || diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt index 5b250d5868..944cb10711 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt @@ -1029,7 +1029,7 @@ internal class DatadogRumMonitor( * Wait for any pending events. This is mostly for integration tests to ensure that the * RUM context is in the correct state before proceeding. */ - @Suppress("unused") + @Suppress("UnusedPrivateFunction", "unused") private fun waitForPendingEvents() { if (!executorService.isShutdown) { @Suppress("UnsafeThirdPartyFunctionCall") // 1 cannot be negative diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/startup/RumSessionScopeStartupManager.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/startup/RumSessionScopeStartupManager.kt index b8e1661427..ff14801bb9 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/startup/RumSessionScopeStartupManager.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/startup/RumSessionScopeStartupManager.kt @@ -155,7 +155,6 @@ internal class RumSessionScopeStartupManagerImpl( ) sendTTIDEvent( - datadogContext = datadogContext, writeScope = writeScope, writer = writer, ttidEvent = ttidEvent, @@ -297,13 +296,12 @@ internal class RumSessionScopeStartupManagerImpl( ) ) - sdkCore.newRumEventWriteOperation(datadogContext, writeScope, writer) { + sdkCore.newRumEventWriteOperation(writeScope, writer) { ttfdEvent }.submit() } private fun sendTTIDEvent( - datadogContext: DatadogContext, writeScope: EventWriteScope, writer: DataWriter, ttidEvent: VitalAppLaunchEvent, @@ -325,7 +323,7 @@ internal class RumSessionScopeStartupManagerImpl( return } - sdkCore.newRumEventWriteOperation(datadogContext, writeScope, writer) { + sdkCore.newRumEventWriteOperation(writeScope, writer) { ttidEvent }.submit() } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/collector/DefaultTimeseriesCollector.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/collector/DefaultTimeseriesCollector.kt index eecdf616c1..a1c05d5f27 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/collector/DefaultTimeseriesCollector.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/timeseries/collector/DefaultTimeseriesCollector.kt @@ -175,7 +175,7 @@ internal class DefaultTimeseriesCollector( // @Suppress("unused") required here to avoid creating wrapping lambda in [onRumContextUpdate] method @AnyThread - private fun tryStartCollection(generation: Int, @Suppress("unused") rumContext: RumContext) { + private fun tryStartCollection(generation: Int, @Suppress("UnusedParameter", "unused") rumContext: RumContext) { if (looperGeneration.getAndSet(generation) != generation) { pipelines.forEach { pipeline -> looper.start( @@ -190,7 +190,7 @@ internal class DefaultTimeseriesCollector( // @Suppress("unused") required here to avoid creating wrapping lambdas at the call sites above @AnyThread - private fun flushPipelines(@Suppress("unused") generation: Int, rumContext: RumContext) { + private fun flushPipelines(@Suppress("UnusedParameter", "unused") generation: Int, rumContext: RumContext) { pipelines.forEach { pipeline -> pipeline.flush(rumContext) } } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/utils/SdkCoreExt.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/utils/SdkCoreExt.kt index ee301b0097..471a9c5f36 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/utils/SdkCoreExt.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/utils/SdkCoreExt.kt @@ -7,7 +7,6 @@ package com.datadog.android.rum.internal.utils import com.datadog.android.api.InternalLogger -import com.datadog.android.api.context.DatadogContext import com.datadog.android.api.feature.EventWriteScope import com.datadog.android.api.feature.FeatureSdkCore import com.datadog.android.api.storage.DataWriter @@ -20,7 +19,6 @@ internal typealias EventOutcomeAction = (rumMonitor: AdvancedRumMonitor) -> Unit internal class WriteOperation( private val sdkCore: FeatureSdkCore, - private val datadogContext: DatadogContext, private val writeScope: EventWriteScope, private val rumDataWriter: DataWriter, private val eventType: EventType, @@ -108,11 +106,10 @@ internal class WriteOperation( } internal fun FeatureSdkCore.newRumEventWriteOperation( - datadogContext: DatadogContext, writeScope: EventWriteScope, rumDataWriter: DataWriter, eventType: EventType = EventType.DEFAULT, eventSource: () -> Any ): WriteOperation { - return WriteOperation(this, datadogContext, writeScope, rumDataWriter, eventType, eventSource) + return WriteOperation(this, writeScope, rumDataWriter, eventType, eventSource) } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSerializerTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSerializerTest.kt index 2418f3502b..5848b94a72 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSerializerTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSerializerTest.kt @@ -953,25 +953,25 @@ internal class RumEventSerializerTest { hasField("configuration") { val configuration = event.telemetry.configuration if (configuration.sessionSampleRate != null) { - hasField("session_sample_rate", configuration.sessionSampleRate!!) + hasField("session_sample_rate", configuration.sessionSampleRate) } if (configuration.telemetrySampleRate != null) { - hasField("telemetry_sample_rate", configuration.telemetrySampleRate!!) + hasField("telemetry_sample_rate", configuration.telemetrySampleRate) } if (configuration.telemetryConfigurationSampleRate != null) { hasField( "telemetry_configuration_sample_rate", - configuration.telemetryConfigurationSampleRate!! + configuration.telemetryConfigurationSampleRate ) } if (configuration.traceSampleRate != null) { - hasField("trace_sample_rate", configuration.traceSampleRate!!) + hasField("trace_sample_rate", configuration.traceSampleRate) } if (configuration.premiumSampleRate != null) { - hasField("premium_sample_rate", configuration.premiumSampleRate!!) + hasField("premium_sample_rate", configuration.premiumSampleRate) } if (configuration.replaySampleRate != null) { - hasField("replay_sample_rate", configuration.replaySampleRate!!) + hasField("replay_sample_rate", configuration.replaySampleRate) } if (configuration.sessionReplaySampleRate != null) { hasField( @@ -995,36 +995,36 @@ internal class RumEventSerializerTest { hasField("use_proxy", configuration.useProxy!!) } if (configuration.useBeforeSend != null) { - hasField("use_before_send", configuration.useBeforeSend!!) + hasField("use_before_send", configuration.useBeforeSend) } if (configuration.silentMultipleInit != null) { - hasField("silent_multiple_init", configuration.silentMultipleInit!!) + hasField("silent_multiple_init", configuration.silentMultipleInit) } if (configuration.trackSessionAcrossSubdomains != null) { hasField( "track_session_across_subdomains", - configuration.trackSessionAcrossSubdomains!! + configuration.trackSessionAcrossSubdomains ) } if (configuration.useCrossSiteSessionCookie != null) { hasField( "use_cross_site_session_cookie", - configuration.useCrossSiteSessionCookie!! + configuration.useCrossSiteSessionCookie ) } if (configuration.useSecureSessionCookie != null) { hasField( "use_secure_session_cookie", - configuration.useSecureSessionCookie!! + configuration.useSecureSessionCookie ) } if (configuration.actionNameAttribute != null) { - hasField("action_name_attribute", configuration.actionNameAttribute!!) + hasField("action_name_attribute", configuration.actionNameAttribute) } if (configuration.useAllowedTracingOrigins != null) { hasField( "use_allowed_tracing_origins", - configuration.useAllowedTracingOrigins!! + configuration.useAllowedTracingOrigins ) } if (configuration.defaultPrivacyLevel != null) { @@ -1033,7 +1033,7 @@ internal class RumEventSerializerTest { if (configuration.useExcludedActivityUrls != null) { hasField( "use_excluded_activity_urls", - configuration.useExcludedActivityUrls!! + configuration.useExcludedActivityUrls ) } if (configuration.trackFrustrations != null) { @@ -1046,21 +1046,21 @@ internal class RumEventSerializerTest { hasField("track_interactions", configuration.trackInteractions!!) } if (configuration.forwardErrorsToLogs != null) { - hasField("forward_errors_to_logs", configuration.forwardErrorsToLogs!!) + hasField("forward_errors_to_logs", configuration.forwardErrorsToLogs) } if (configuration.forwardConsoleLogs != null) { - hasField("forward_console_logs", configuration.forwardConsoleLogs!!) + hasField("forward_console_logs", configuration.forwardConsoleLogs) } if (configuration.forwardReports != null) { - hasField("forward_reports", configuration.forwardReports!!) + hasField("forward_reports", configuration.forwardReports) } if (configuration.useLocalEncryption != null) { - hasField("use_local_encryption", configuration.useLocalEncryption!!) + hasField("use_local_encryption", configuration.useLocalEncryption) } if (configuration.viewTrackingStrategy != null) { hasField( "view_tracking_strategy", - configuration.viewTrackingStrategy!!.toJson() + configuration.viewTrackingStrategy.toJson() ) } if (configuration.trackBackgroundEvents != null) { @@ -1082,7 +1082,7 @@ internal class RumEventSerializerTest { hasField("track_network_requests", configuration.trackNetworkRequests!!) } if (configuration.useTracing != null) { - hasField("use_tracing", configuration.useTracing!!) + hasField("use_tracing", configuration.useTracing) } if (configuration.trackNativeViews != null) { hasField("track_native_views", configuration.trackNativeViews!!) @@ -1112,13 +1112,13 @@ internal class RumEventSerializerTest { ) } if (configuration.batchSize != null) { - hasField("batch_size", configuration.batchSize!!) + hasField("batch_size", configuration.batchSize) } if (configuration.batchUploadFrequency != null) { - hasField("batch_upload_frequency", configuration.batchUploadFrequency!!) + hasField("batch_upload_frequency", configuration.batchUploadFrequency) } if (configuration.isMainProcess != null) { - hasField("is_main_process", configuration.isMainProcess!!) + hasField("is_main_process", configuration.isMainProcess) } } } @@ -1183,7 +1183,7 @@ internal class RumEventSerializerTest { hasField("usage") { when (event.telemetry.usage) { is TelemetryUsageEvent.Usage.AddViewLoadingTime -> { - val usage = event.telemetry.usage as TelemetryUsageEvent.Usage.AddViewLoadingTime + val usage = event.telemetry.usage hasField("no_view", usage.noView) hasField("no_active_view", usage.noActiveView) hasField("overwritten", usage.overwritten) @@ -1197,30 +1197,28 @@ internal class RumEventSerializerTest { if (event.telemetry.device != null) { hasField("device") { val device = event.telemetry.device - checkNotNull(device) if (device.architecture != null) { - hasField("architecture", device.architecture!!) + hasField("architecture", device.architecture) } if (device.brand != null) { - hasField("brand", device.brand!!) + hasField("brand", device.brand) } if (device.model != null) { - hasField("model", device.model!!) + hasField("model", device.model) } } } if (event.telemetry.os != null) { hasField("os") { val os = event.telemetry.os - checkNotNull(os) if (os.build != null) { - hasField("build", os.build!!) + hasField("build", os.build) } if (os.name != null) { - hasField("name", os.name!!) + hasField("name", os.name) } if (os.version != null) { - hasField("version", os.version!!) + hasField("version", os.version) } } } @@ -1771,10 +1769,9 @@ internal class RumEventSerializerTest { } val faultyEvent = event.copy( usr = event.usr?.copy( - additionalProperties = event.usr?.additionalProperties - ?.toMutableMap() - ?.apply { put(faultyKey, faultyObject) } - .orEmpty() + additionalProperties = event.usr.additionalProperties + .toMutableMap() + .apply { put(faultyKey, faultyObject) } .toMutableMap() ) ) @@ -1808,10 +1805,9 @@ internal class RumEventSerializerTest { } val faultyEvent = event.copy( account = event.account?.copy( - additionalProperties = event.account?.additionalProperties - ?.toMutableMap() - ?.apply { put(faultyKey, faultyObject) } - .orEmpty() + additionalProperties = event.account.additionalProperties + .toMutableMap() + .apply { put(faultyKey, faultyObject) } .toMutableMap() ) ) @@ -1844,10 +1840,9 @@ internal class RumEventSerializerTest { } val faultyEvent = event.copy( context = event.context?.copy( - additionalProperties = event.context?.additionalProperties - ?.toMutableMap() - ?.apply { put(faultyKey, faultyObject) } - .orEmpty() + additionalProperties = event.context.additionalProperties + .toMutableMap() + .apply { put(faultyKey, faultyObject) } .toMutableMap() ) ) @@ -1878,10 +1873,9 @@ internal class RumEventSerializerTest { } val faultyEvent = event.copy( usr = event.usr?.copy( - additionalProperties = event.usr?.additionalProperties - ?.toMutableMap() - ?.apply { put(faultyKey, faultyObject) } - .orEmpty() + additionalProperties = event.usr.additionalProperties + .toMutableMap() + .apply { put(faultyKey, faultyObject) } .toMutableMap() ) ) @@ -1915,10 +1909,9 @@ internal class RumEventSerializerTest { } val faultyEvent = event.copy( account = event.account?.copy( - additionalProperties = event.account?.additionalProperties - ?.toMutableMap() - ?.apply { put(faultyKey, faultyObject) } - .orEmpty() + additionalProperties = event.account.additionalProperties + .toMutableMap() + .apply { put(faultyKey, faultyObject) } .toMutableMap() ) ) @@ -1951,10 +1944,9 @@ internal class RumEventSerializerTest { } val faultyEvent = event.copy( context = event.context?.copy( - additionalProperties = event.context?.additionalProperties - ?.toMutableMap() - ?.apply { put(faultyKey, faultyObject) } - .orEmpty() + additionalProperties = event.context.additionalProperties + .toMutableMap() + .apply { put(faultyKey, faultyObject) } .toMutableMap() ) ) @@ -1985,10 +1977,9 @@ internal class RumEventSerializerTest { } val faultyEvent = event.copy( usr = event.usr?.copy( - additionalProperties = event.usr?.additionalProperties - ?.toMutableMap() - ?.apply { put(faultyKey, faultyObject) } - .orEmpty() + additionalProperties = event.usr.additionalProperties + .toMutableMap() + .apply { put(faultyKey, faultyObject) } .toMutableMap() ) ) @@ -2022,10 +2013,9 @@ internal class RumEventSerializerTest { } val faultyEvent = event.copy( account = event.account?.copy( - additionalProperties = event.account?.additionalProperties - ?.toMutableMap() - ?.apply { put(faultyKey, faultyObject) } - .orEmpty() + additionalProperties = event.account.additionalProperties + .toMutableMap() + .apply { put(faultyKey, faultyObject) } .toMutableMap() ) ) @@ -2058,10 +2048,9 @@ internal class RumEventSerializerTest { } val faultyEvent = event.copy( context = event.context?.copy( - additionalProperties = event.context?.additionalProperties - ?.toMutableMap() - ?.apply { put(faultyKey, faultyObject) } - .orEmpty() + additionalProperties = event.context.additionalProperties + .toMutableMap() + .apply { put(faultyKey, faultyObject) } .toMutableMap() ) ) @@ -2092,10 +2081,9 @@ internal class RumEventSerializerTest { } val faultyEvent = event.copy( usr = event.usr?.copy( - additionalProperties = event.usr?.additionalProperties - ?.toMutableMap() - ?.apply { put(faultyKey, faultyObject) } - .orEmpty() + additionalProperties = event.usr.additionalProperties + .toMutableMap() + .apply { put(faultyKey, faultyObject) } .toMutableMap() ) ) @@ -2129,10 +2117,9 @@ internal class RumEventSerializerTest { } val faultyEvent = event.copy( account = event.account?.copy( - additionalProperties = event.account?.additionalProperties - ?.toMutableMap() - ?.apply { put(faultyKey, faultyObject) } - .orEmpty() + additionalProperties = event.account.additionalProperties + .toMutableMap() + .apply { put(faultyKey, faultyObject) } .toMutableMap() ) ) @@ -2165,10 +2152,9 @@ internal class RumEventSerializerTest { } val faultyEvent = event.copy( context = event.context?.copy( - additionalProperties = event.context?.additionalProperties - ?.toMutableMap() - ?.apply { put(faultyKey, faultyObject) } - .orEmpty() + additionalProperties = event.context.additionalProperties + .toMutableMap() + .apply { put(faultyKey, faultyObject) } .toMutableMap() ) ) @@ -2199,10 +2185,9 @@ internal class RumEventSerializerTest { } val faultyEvent = event.copy( usr = event.usr?.copy( - additionalProperties = event.usr?.additionalProperties - ?.toMutableMap() - ?.apply { put(faultyKey, faultyObject) } - .orEmpty() + additionalProperties = event.usr.additionalProperties + .toMutableMap() + .apply { put(faultyKey, faultyObject) } .toMutableMap() ) ) @@ -2236,10 +2221,9 @@ internal class RumEventSerializerTest { } val faultyEvent = event.copy( account = event.account?.copy( - additionalProperties = event.account?.additionalProperties - ?.toMutableMap() - ?.apply { put(faultyKey, faultyObject) } - .orEmpty() + additionalProperties = event.account.additionalProperties + .toMutableMap() + .apply { put(faultyKey, faultyObject) } .toMutableMap() ) ) @@ -2272,10 +2256,9 @@ internal class RumEventSerializerTest { } val faultyEvent = event.copy( context = event.context?.copy( - additionalProperties = event.context?.additionalProperties - ?.toMutableMap() - ?.apply { put(faultyKey, faultyObject) } - .orEmpty() + additionalProperties = event.context.additionalProperties + .toMutableMap() + .apply { put(faultyKey, faultyObject) } .toMutableMap() ) ) @@ -2306,10 +2289,9 @@ internal class RumEventSerializerTest { } val faultyEvent = event.copy( usr = event.usr?.copy( - additionalProperties = event.usr?.additionalProperties - ?.toMutableMap() - ?.apply { put(faultyKey, faultyObject) } - .orEmpty() + additionalProperties = event.usr.additionalProperties + .toMutableMap() + .apply { put(faultyKey, faultyObject) } .toMutableMap() ) ) @@ -2343,10 +2325,9 @@ internal class RumEventSerializerTest { } val faultyEvent = event.copy( account = event.account?.copy( - additionalProperties = event.account?.additionalProperties - ?.toMutableMap() - ?.apply { put(faultyKey, faultyObject) } - .orEmpty() + additionalProperties = event.account.additionalProperties + .toMutableMap() + .apply { put(faultyKey, faultyObject) } .toMutableMap() ) ) @@ -2379,10 +2360,9 @@ internal class RumEventSerializerTest { } val faultyEvent = event.copy( context = event.context?.copy( - additionalProperties = event.context?.additionalProperties - ?.toMutableMap() - ?.apply { put(faultyKey, faultyObject) } - .orEmpty() + additionalProperties = event.context.additionalProperties + .toMutableMap() + .apply { put(faultyKey, faultyObject) } .toMutableMap() ) ) @@ -2413,10 +2393,9 @@ internal class RumEventSerializerTest { } val faultyEvent = event.copy( usr = event.usr?.copy( - additionalProperties = event.usr?.additionalProperties - ?.toMutableMap() - ?.apply { put(faultyKey, faultyObject) } - .orEmpty() + additionalProperties = event.usr.additionalProperties + .toMutableMap() + .apply { put(faultyKey, faultyObject) } .toMutableMap() ) ) @@ -2450,10 +2429,9 @@ internal class RumEventSerializerTest { } val faultyEvent = event.copy( context = event.context?.copy( - additionalProperties = event.context?.additionalProperties - ?.toMutableMap() - ?.apply { put(faultyKey, faultyObject) } - .orEmpty() + additionalProperties = event.context.additionalProperties + .toMutableMap() + .apply { put(faultyKey, faultyObject) } .toMutableMap() ) ) diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScopeTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScopeTest.kt index ff43e52757..93ab019f51 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScopeTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScopeTest.kt @@ -7209,6 +7209,7 @@ internal class RumViewScopeTest { InternalLogger.Level.DEBUG, InternalLogger.Target.USER, RumViewScope.ADDING_VIEW_LOADING_TIME_DEBUG_MESSAGE_FORMAT.format( + Locale.US, expectedViewLoadingTime, testedScope.key.name ) diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/model/ViewEventDiffTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/model/ViewEventDiffTest.kt index 02a8a70dec..e2868a909f 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/model/ViewEventDiffTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/model/ViewEventDiffTest.kt @@ -244,7 +244,7 @@ internal class ViewEventDiffTest { // Then — only the newly appended frame is returned assertThat(result.view.slowFrames).hasSize(1) assertThat(result.view.slowFrames!!.first().start).isEqualTo(newFrame.start) - assertThat(result.view.slowFrames!!.first().duration).isEqualTo(newFrame.duration) + assertThat(result.view.slowFrames.first().duration).isEqualTo(newFrame.duration) } @Test @@ -269,7 +269,7 @@ internal class ViewEventDiffTest { // Then — only the new frame (start > last known start) must be returned assertThat(result.view.slowFrames).hasSize(1) assertThat(result.view.slowFrames!!.first().start).isEqualTo(newFrame.start) - assertThat(result.view.slowFrames!!.first().duration).isEqualTo(newFrame.duration) + assertThat(result.view.slowFrames.first().duration).isEqualTo(newFrame.duration) } @Test diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/net/RumRequestFactoryTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/net/RumRequestFactoryTest.kt index f5528232ca..87a561db22 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/net/RumRequestFactoryTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/net/RumRequestFactoryTest.kt @@ -68,17 +68,15 @@ internal class RumRequestFactoryTest { @Test fun `M create a proper request W create()`( @Forgery batchData: List, - @StringForgery batchMetadata: String, forge: Forge ) { // Given - val batchMetadata = forge.aNullable { batchMetadata.toByteArray() } + val batchMetadata = forge.aNullable { forge.aString().toByteArray() } // When val request = testedFactory.create(fakeDatadogContext, fakeExecutionContext, batchData, batchMetadata) // Then - requireNotNull(request) val expectedUrl = expectedUrl(fakeDatadogContext.site.intakeEndpoint + "/api/v2/rum") assertThat(request.url).isEqualTo(expectedUrl) assertThat(request.contentType).isEqualTo(RequestFactory.CONTENT_TYPE_TEXT_UTF8) @@ -113,7 +111,6 @@ internal class RumRequestFactoryTest { fun `M create a proper request W create() { custom endpoint }`( @StringForgery(regex = "https://[a-z]+\\.com(/[a-z]+)+") fakeEndpoint: String, @Forgery batchData: List, - @StringForgery batchMetadata: String, forge: Forge ) { // Given @@ -122,7 +119,7 @@ internal class RumRequestFactoryTest { viewEventFilter = mockViewEventFilter, internalLogger = InternalLogger.UNBOUND ) - val batchMetadata = forge.aNullable { batchMetadata.toByteArray() } + val batchMetadata = forge.aNullable { forge.aString().toByteArray() } // When val request = testedFactory.create(fakeDatadogContext, fakeExecutionContext, batchData, batchMetadata) diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/utils/SdkCoreExtTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/utils/SdkCoreExtTest.kt index 4d812b49ce..915518fdca 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/utils/SdkCoreExtTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/utils/SdkCoreExtTest.kt @@ -7,7 +7,6 @@ package com.datadog.android.rum.internal.utils import com.datadog.android.api.InternalLogger -import com.datadog.android.api.context.DatadogContext import com.datadog.android.api.feature.EventWriteScope import com.datadog.android.api.storage.DataWriter import com.datadog.android.api.storage.EventBatchWriter @@ -67,9 +66,6 @@ internal class SdkCoreExtTest { private val mockSdkCore get() = rumMonitor.mockSdkCore - @Forgery - lateinit var fakeDatadogContext: DatadogContext - @Forgery lateinit var fakeEventType: EventType @@ -89,7 +85,7 @@ internal class SdkCoreExtTest { val fakeEvent = Any() // When - mockSdkCore.newRumEventWriteOperation(fakeDatadogContext, mockEventWriteScope, mockWriter) { fakeEvent } + mockSdkCore.newRumEventWriteOperation(mockEventWriteScope, mockWriter) { fakeEvent } .submit() // Then @@ -104,7 +100,6 @@ internal class SdkCoreExtTest { // When mockSdkCore.newRumEventWriteOperation( - fakeDatadogContext, mockEventWriteScope, mockWriter, fakeEventType @@ -123,7 +118,6 @@ internal class SdkCoreExtTest { // When mockSdkCore.newRumEventWriteOperation( - fakeDatadogContext, mockEventWriteScope, mockWriter, fakeEventType @@ -147,7 +141,6 @@ internal class SdkCoreExtTest { // When mockSdkCore.newRumEventWriteOperation( - fakeDatadogContext, mockEventWriteScope, mockWriter, fakeEventType @@ -178,7 +171,6 @@ internal class SdkCoreExtTest { // When mockSdkCore.newRumEventWriteOperation( - fakeDatadogContext, mockEventWriteScope, mockWriter, fakeEventType @@ -208,7 +200,6 @@ internal class SdkCoreExtTest { // When mockSdkCore.newRumEventWriteOperation( - fakeDatadogContext, mockEventWriteScope, mockWriter, fakeEventType @@ -237,7 +228,6 @@ internal class SdkCoreExtTest { // When mockSdkCore.newRumEventWriteOperation( - fakeDatadogContext, mockEventWriteScope, mockWriter, fakeEventType @@ -262,7 +252,6 @@ internal class SdkCoreExtTest { // When mockSdkCore.newRumEventWriteOperation( - fakeDatadogContext, mockEventWriteScope, mockWriter, fakeEventType diff --git a/features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/ImageSemanticsNodeMapperTest.kt b/features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/ImageSemanticsNodeMapperTest.kt index 7a4858c6bb..7d476fffca 100644 --- a/features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/ImageSemanticsNodeMapperTest.kt +++ b/features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/ImageSemanticsNodeMapperTest.kt @@ -9,7 +9,6 @@ package com.datadog.android.sessionreplay.compose.internal.mappers.semantics import android.graphics.Bitmap import androidx.compose.ui.Alignment import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.semantics.SemanticsNode import com.datadog.android.api.InternalLogger import com.datadog.android.sessionreplay.ImagePrivacy import com.datadog.android.sessionreplay.compose.internal.data.BitmapInfo @@ -54,9 +53,6 @@ internal class ImageSemanticsNodeMapperTest : AbstractSemanticsNodeMapperTest() @Mock private lateinit var mockInternalLogger: InternalLogger - @Mock - private lateinit var mockSemanticsNode: SemanticsNode - @Mock private lateinit var mockAsyncJobStatusCallback: AsyncJobStatusCallback diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/processor/MutationResolver.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/processor/MutationResolver.kt index 78ff0bdb24..98c935e0a9 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/processor/MutationResolver.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/processor/MutationResolver.kt @@ -39,7 +39,7 @@ internal class MutationResolver(private val internalLogger: InternalLogger) { * @return the mutations as [MobileSegment.MobileIncrementalData.MobileMutationData] * describing changes from `oldArray` to `newArray` or null if no difference was found. */ - @Suppress("LongMethod", "ComplexMethod", "NestedBlockDepth", "ReturnCount") + @Suppress("LongMethod", "CyclomaticComplexMethod", "NestedBlockDepth", "ReturnCount") internal fun resolveMutations( oldSnapshot: List, newSnapshot: List @@ -203,7 +203,7 @@ internal class MutationResolver(private val internalLogger: InternalLogger) { } // TODO RUM-841 Use the `diff` method int the ShapeWireframe type when available - @Suppress("ComplexMethod") + @Suppress("CyclomaticComplexMethod") private fun resolveShapeMutation( prevWireframe: MobileSegment.Wireframe.ShapeWireframe, currentWireframe: MobileSegment.Wireframe.ShapeWireframe @@ -472,7 +472,7 @@ internal class MutationResolver(private val internalLogger: InternalLogger) { } // TODO RUM-841 Use the `diff` method int the TextWireframe type when available - @Suppress("ComplexMethod") + @Suppress("CyclomaticComplexMethod") private fun resolveTextMutation( prevWireframe: MobileSegment.Wireframe.TextWireframe, currentWireframe: MobileSegment.Wireframe.TextWireframe diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/SessionReplayRecorder.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/SessionReplayRecorder.kt index 926e67c1e3..6013a50924 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/SessionReplayRecorder.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/SessionReplayRecorder.kt @@ -438,6 +438,7 @@ internal class SessionReplayRecorder : OnWindowRefreshedCallback, Recorder { // a window already open (e.g. a dialog) isn't reported through the Activity lifecycle private fun resolveUntrackedWindows(decorViews: List, knownWindows: List): List { + @Suppress("CouldBeSequence") return decorViews .filterNot { it.width == 0 || it.height == 0 } .mapNotNull { windowFromDecorView(it) } diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/SnapshotProducer.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/SnapshotProducer.kt index adc4c4d595..263b683428 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/SnapshotProducer.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/SnapshotProducer.kt @@ -81,7 +81,7 @@ internal class SnapshotProducer( return rootNode } - @Suppress("ComplexMethod", "ReturnCount", "LongParameterList") + @Suppress("CyclomaticComplexMethod", "ReturnCount", "LongParameterList") @UiThread private fun convertViewToNode( view: View, diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayConfigurationBuilderTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayConfigurationBuilderTest.kt index 528b01fd3b..4c10399adf 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayConfigurationBuilderTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayConfigurationBuilderTest.kt @@ -399,7 +399,7 @@ internal class SessionReplayConfigurationBuilderTest { testedBuilder.build() - val expected = String.format(DUPLICATE_MAPPER_DETECTED, ImageView::class.java) + val expected = String.format(Locale.US, DUPLICATE_MAPPER_DETECTED, ImageView::class.java) // Then mockLogger.verifyLog( diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/gson/GsonExtTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/gson/GsonExtTest.kt index 425dec8d68..59e110d792 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/gson/GsonExtTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/gson/GsonExtTest.kt @@ -24,6 +24,7 @@ import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoSettings import org.mockito.quality.Strictness +import java.util.Locale @Extensions( ExtendWith(MockitoExtension::class), @@ -48,6 +49,7 @@ internal class GsonExtTest { fun `M return null W safeGetAsJsonObject{a JsonArray}`(@Forgery fakeJsonArray: JsonArray) { // Given val expectedLogMessage = BROKEN_JSON_ERROR_MESSAGE_FORMAT.format( + Locale.US, fakeJsonArray.toString(), JSON_OBJECT_TYPE ) @@ -67,6 +69,7 @@ internal class GsonExtTest { ) { // Given val expectedLogMessage = BROKEN_JSON_ERROR_MESSAGE_FORMAT.format( + Locale.US, fakeJsonPrimitive.toString(), JSON_OBJECT_TYPE ) @@ -95,6 +98,7 @@ internal class GsonExtTest { ) { // Given val expectedLogMessage = BROKEN_JSON_ERROR_MESSAGE_FORMAT.format( + Locale.US, fakeJsonPrimitive.toString(), JSON_ARRAY_TYPE ) @@ -125,6 +129,7 @@ internal class GsonExtTest { // Given val fakeNonLongJsonPrimitive = JsonPrimitive(forge.anAlphabeticalString()) val expectedLogMessage = BROKEN_JSON_ERROR_MESSAGE_FORMAT.format( + Locale.US, fakeNonLongJsonPrimitive.toString(), JSON_PRIMITIVE_TYPE ) diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/WindowReflectionUtilsTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/WindowReflectionUtilsTest.kt index 569843026b..cfc69a563e 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/WindowReflectionUtilsTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/WindowReflectionUtilsTest.kt @@ -52,7 +52,7 @@ internal class WindowReflectionUtilsTest { // Given val fakeWindow = mock() val fakeDecorLike = object : View(mock()) { - @Suppress("unused") + @Suppress("UnusedPrivateProperty", "unused") private val mWindow: Window = fakeWindow } diff --git a/features/dd-sdk-android-trace/src/main/kotlin/com/datadog/android/trace/internal/DatadogPropagationHelper.kt b/features/dd-sdk-android-trace/src/main/kotlin/com/datadog/android/trace/internal/DatadogPropagationHelper.kt index e31e7d57a1..404de4b065 100644 --- a/features/dd-sdk-android-trace/src/main/kotlin/com/datadog/android/trace/internal/DatadogPropagationHelper.kt +++ b/features/dd-sdk-android-trace/src/main/kotlin/com/datadog/android/trace/internal/DatadogPropagationHelper.kt @@ -23,6 +23,7 @@ import com.datadog.trace.core.propagation.B3HttpCodec import com.datadog.trace.core.propagation.DatadogHttpCodec import com.datadog.trace.core.propagation.ExtractedContext import com.datadog.trace.core.propagation.W3CHttpCodec +import java.util.Locale /** * For internal usage only. @@ -439,6 +440,7 @@ class DatadogPropagationHelper internal constructor() { W3CHttpCodec.TRACE_PARENT_KEY, @Suppress("UnsafeThirdPartyFunctionCall") // Format string is static W3C_TRACE_PARENT_DROP_SAMPLING_DECISION.format( + Locale.US, traceId.padStart(length = W3C_TRACE_ID_LENGTH, padChar = '0'), spanId.padStart(length = W3C_PARENT_ID_LENGTH, padChar = '0') ) @@ -446,7 +448,7 @@ class DatadogPropagationHelper internal constructor() { // TODO RUM-2121 3rd party vendor information will be erased @Suppress("UnsafeThirdPartyFunctionCall") // Format string is static var traceStateHeader = W3C_TRACE_STATE_DROP_SAMPLING_DECISION - .format(spanId.padStart(length = W3C_PARENT_ID_LENGTH, padChar = '0')) + .format(Locale.US, spanId.padStart(length = W3C_PARENT_ID_LENGTH, padChar = '0')) if (traceOrigin != null) { traceStateHeader += ";o:$traceOrigin" } diff --git a/features/dd-sdk-android-trace/src/main/kotlin/com/datadog/android/trace/internal/_TraceInternalProxy.kt b/features/dd-sdk-android-trace/src/main/kotlin/com/datadog/android/trace/internal/_TraceInternalProxy.kt index 4f18dc6f9f..1c3a6bf835 100644 --- a/features/dd-sdk-android-trace/src/main/kotlin/com/datadog/android/trace/internal/_TraceInternalProxy.kt +++ b/features/dd-sdk-android-trace/src/main/kotlin/com/datadog/android/trace/internal/_TraceInternalProxy.kt @@ -20,7 +20,7 @@ import com.datadog.trace.core.propagation.Baggage * Provides implementation for specific interfaces to dependent modules */ @InternalApi -@Suppress("ClassName") +@Suppress("ClassName", "ClassNaming") object _TraceInternalProxy { /** * Provides a mechanism for converting Datadog span IDs between decimal and hexadecimal representations. diff --git a/features/dd-sdk-android-trace/src/test/kotlin/com/datadog/android/trace/internal/domain/event/SpanEventSerializerTest.kt b/features/dd-sdk-android-trace/src/test/kotlin/com/datadog/android/trace/internal/domain/event/SpanEventSerializerTest.kt index 533e18b3c8..7ac9e33718 100644 --- a/features/dd-sdk-android-trace/src/test/kotlin/com/datadog/android/trace/internal/domain/event/SpanEventSerializerTest.kt +++ b/features/dd-sdk-android-trace/src/test/kotlin/com/datadog/android/trace/internal/domain/event/SpanEventSerializerTest.kt @@ -229,8 +229,7 @@ internal class SpanEventSerializerTest { meta = fakeSpanEvent.meta.copy( account = fakeSpanEvent.meta.account?.copy( additionalProperties = fakeSpanEvent.meta.account - ?.additionalProperties - .orEmpty() + .additionalProperties .toMutableMap() .apply { put(faultyKey, faultyObject) } ) diff --git a/features/dd-sdk-android-trace/src/test/kotlin/com/datadog/android/trace/internal/net/TracesRequestFactoryTest.kt b/features/dd-sdk-android-trace/src/test/kotlin/com/datadog/android/trace/internal/net/TracesRequestFactoryTest.kt index 0db94910fe..6d6e85dd15 100644 --- a/features/dd-sdk-android-trace/src/test/kotlin/com/datadog/android/trace/internal/net/TracesRequestFactoryTest.kt +++ b/features/dd-sdk-android-trace/src/test/kotlin/com/datadog/android/trace/internal/net/TracesRequestFactoryTest.kt @@ -52,12 +52,11 @@ internal class TracesRequestFactoryTest { @Test fun `M create a proper request W create()`( @Forgery batchData: List, - @StringForgery batchMetadata: String, @Forgery executionContext: RequestExecutionContext, forge: Forge ) { // Given - val batchMetadata = forge.aNullable { batchMetadata.toByteArray() } + val batchMetadata = forge.aNullable { forge.aString().toByteArray() } // When val request = testedFactory.create(fakeDatadogContext, executionContext, batchData, batchMetadata) @@ -89,13 +88,12 @@ internal class TracesRequestFactoryTest { fun `M create a proper request W create() { custom endpoint }`( @StringForgery(regex = "https://[a-z]+\\.com(/[a-z]+)+") fakeEndpoint: String, @Forgery batchData: List, - @StringForgery batchMetadata: String, @Forgery executionContext: RequestExecutionContext, forge: Forge ) { // Given testedFactory = TracesRequestFactory(customEndpointUrl = fakeEndpoint, internalLogger = InternalLogger.UNBOUND) - val batchMetadata = forge.aNullable { batchMetadata.toByteArray() } + val batchMetadata = forge.aNullable { forge.aString().toByteArray() } // When val request = testedFactory.create(fakeDatadogContext, executionContext, batchData, batchMetadata) diff --git a/features/dd-sdk-android-webview/src/main/kotlin/com/datadog/android/webview/internal/replay/WebViewReplayEventMapper.kt b/features/dd-sdk-android-webview/src/main/kotlin/com/datadog/android/webview/internal/replay/WebViewReplayEventMapper.kt index ef2b079aac..f50199c752 100644 --- a/features/dd-sdk-android-webview/src/main/kotlin/com/datadog/android/webview/internal/replay/WebViewReplayEventMapper.kt +++ b/features/dd-sdk-android-webview/src/main/kotlin/com/datadog/android/webview/internal/replay/WebViewReplayEventMapper.kt @@ -34,7 +34,7 @@ internal class WebViewReplayEventMapper( val viewDataObject = event.get(VIEW_OBJECT_KEY)?.asJsonObject val viewId = viewDataObject?.get(VIEW_ID_KEY)?.asString ?: error(BROWSER_EVENT_MISSING_VIEW_DATA_ERROR_MESSAGE) - event.get(EVENT_KEY)?.asJsonObject?.let { record -> + return event.get(EVENT_KEY)?.asJsonObject?.let { record -> val timeOffset = offsetProvider.getOffset(viewId, datadogContext) record.get(TIMESTAMP_KEY)?.let { timestamp -> val asLong = timestamp.asLong @@ -42,7 +42,7 @@ internal class WebViewReplayEventMapper( record.addProperty(TIMESTAMP_KEY, correctedTimestamp) } record.addProperty(SLOT_ID_KEY, webViewId) - return bundleIntoEnrichedRecord(record, viewId, rumContext) + bundleIntoEnrichedRecord(record, viewId, rumContext) } ?: error(BROWSER_EVENT_MISSING_RECORD_ERROR_MESSAGE) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0697d84b36..9ece4b3e12 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -64,7 +64,7 @@ kover = "0.9.8" kspTesting = "0.8.0" # Tools -detekt = "1.23.8" +detekt = "2.0.0-alpha.1" dokka = "2.2.0" ktlint = "1.5.0" ktlintGradlePlugin = "14.2.0" @@ -118,7 +118,7 @@ androidToolsGradlePluginApi = { module = "com.android.tools.build:gradle-api", v apolloRuntime = { module = "com.apollographql.apollo:apollo-runtime", version.ref = "apollo" } kotlinGradlePluginApi = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin-api", version.ref = "kotlinBuildscript" } dokkaGradlePlugin = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "dokka" } -detektGradlePlugin = { module = "io.gitlab.arturbosch.detekt:detekt-gradle-plugin", version.ref = "detekt" } +detektGradlePlugin = { module = "dev.detekt:detekt-gradle-plugin", version.ref = "detekt" } unmockGradlePlugin = { module = "com.github.bjoernq:unmockplugin", version.ref = "unmock" } # Annotation processors @@ -216,8 +216,8 @@ androidLintTests = { module = "com.android.tools.lint:lint-tests", version.ref = systemStubsJupiter = { module = "uk.org.webcompere:system-stubs-jupiter", version.ref = "systemStubsJupiter" } # Tools -detektApi = { module = "io.gitlab.arturbosch.detekt:detekt-api", version.ref = "detekt" } -detektTest = { module = "io.gitlab.arturbosch.detekt:detekt-test", version.ref = "detekt" } +detektApi = { module = "dev.detekt:detekt-api", version.ref = "detekt" } +detektTest = { module = "dev.detekt:detekt-test", version.ref = "detekt" } ktlintGradlePlugin = { module = "org.jlleitschuh.gradle.ktlint:org.jlleitschuh.gradle.ktlint.gradle.plugin", version.ref = "ktlintGradlePlugin" } okHttpMock = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "okHttp" } robolectric = { module = "org.robolectric:android-all", version.ref = "robolectric" } @@ -392,4 +392,4 @@ binaryCompatibilityGradlePlugin = { id = "org.jetbrains.kotlinx.binary-compatibi kotlinxSerializationPlugin = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlinBuildscript" } koverPlugin = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" } ktlintGradlePlugin = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlintGradlePlugin" } -detektGradlePlugin = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" } +detektGradlePlugin = { id = "dev.detekt", version.ref = "detekt" } diff --git a/integrations/dd-sdk-android-compose/src/main/kotlin/com/datadog/android/compose/internal/InternalInteractionTracking.kt b/integrations/dd-sdk-android-compose/src/main/kotlin/com/datadog/android/compose/internal/InternalInteractionTracking.kt index 0ea33ec073..c3f9a5470d 100644 --- a/integrations/dd-sdk-android-compose/src/main/kotlin/com/datadog/android/compose/internal/InternalInteractionTracking.kt +++ b/integrations/dd-sdk-android-compose/src/main/kotlin/com/datadog/android/compose/internal/InternalInteractionTracking.kt @@ -7,7 +7,6 @@ package com.datadog.android.compose.internal import android.util.Log -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ScrollState import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.ScrollableState @@ -85,6 +84,9 @@ internal suspend fun trackScroll( ) } +// we re-throw CancellationException immediately, there is no violation +// by some reason suppression cannot be applied at the catch block +@Suppress("SuspendFunSwallowedCancellation") internal suspend fun trackDragInteraction( interactionSource: InteractionSource, onStart: ( @@ -235,7 +237,6 @@ private fun resolveDragDirection( } } -@OptIn(ExperimentalFoundationApi::class) @Suppress("LongParameterList") private fun reportSwipeInteraction( rumMonitor: RumMonitor, diff --git a/integrations/dd-sdk-android-okhttp/src/main/kotlin/com/datadog/android/okhttp/trace/TracingInterceptor.kt b/integrations/dd-sdk-android-okhttp/src/main/kotlin/com/datadog/android/okhttp/trace/TracingInterceptor.kt index 1917aad053..e144cd0b43 100644 --- a/integrations/dd-sdk-android-okhttp/src/main/kotlin/com/datadog/android/okhttp/trace/TracingInterceptor.kt +++ b/integrations/dd-sdk-android-okhttp/src/main/kotlin/com/datadog/android/okhttp/trace/TracingInterceptor.kt @@ -51,6 +51,7 @@ import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response import java.net.HttpURLConnection +import java.util.Locale import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicReference import com.datadog.android.okhttp.TraceContext as DeprecatedTraceContext @@ -590,6 +591,7 @@ internal constructor( W3C_TRACEPARENT_KEY, @Suppress("UnsafeThirdPartyFunctionCall") // Format string is static W3C_TRACEPARENT_DROP_SAMPLING_DECISION.format( + Locale.US, traceId.padStart(length = W3C_TRACE_ID_LENGTH, padChar = '0'), spanId.padStart(length = W3C_PARENT_ID_LENGTH, padChar = '0') ) @@ -597,7 +599,7 @@ internal constructor( // TODO RUM-2121 3rd party vendor information will be erased @Suppress("UnsafeThirdPartyFunctionCall") // Format string is static var traceStateHeader = W3C_TRACESTATE_DROP_SAMPLING_DECISION - .format(spanId.padStart(length = W3C_PARENT_ID_LENGTH, padChar = '0')) + .format(Locale.US, spanId.padStart(length = W3C_PARENT_ID_LENGTH, padChar = '0')) if (traceOrigin != null) { traceStateHeader += ";o:$traceOrigin" } diff --git a/integrations/dd-sdk-android-okhttp/src/test/kotlin/com/datadog/android/okhttp/trace/TracingInterceptorTestsExt.kt b/integrations/dd-sdk-android-okhttp/src/test/kotlin/com/datadog/android/okhttp/trace/TracingInterceptorTestsExt.kt index bea3d68f53..933c4fe750 100644 --- a/integrations/dd-sdk-android-okhttp/src/test/kotlin/com/datadog/android/okhttp/trace/TracingInterceptorTestsExt.kt +++ b/integrations/dd-sdk-android-okhttp/src/test/kotlin/com/datadog/android/okhttp/trace/TracingInterceptorTestsExt.kt @@ -37,7 +37,7 @@ internal fun DatadogPropagation.wheneverInjectThenThrow(throwable: Throwable) { internal fun DatadogPropagation.wheneverInjectThenValueToHeaders(key: String, value: String) { doAnswer { invocation -> val carrier = invocation.getArgument(1) - val setter = invocation.getArgument<(carrier: Request.Builder, key: String, value: String) -> Unit>(2) + val setter = invocation.getArgument<(Request.Builder, String, String) -> Unit>(2) setter.invoke(carrier, key, value) } .whenever(this) diff --git a/tools/detekt/build.gradle.kts b/tools/detekt/build.gradle.kts index 19b4a79cbc..4ea8c9e968 100644 --- a/tools/detekt/build.gradle.kts +++ b/tools/detekt/build.gradle.kts @@ -4,6 +4,8 @@ * Copyright 2016-Present Datadog, Inc. */ +import org.jetbrains.kotlin.gradle.dsl.KotlinVersion + plugins { // Build id("org.jetbrains.kotlin.jvm") @@ -14,18 +16,18 @@ plugins { } dependencies { - // pin it temporarily to Kotlin Runtime, because Detekt 1.23.8 is not capable processing Kotlin 2.2 stdlib - implementation(libs.kotlinRuntime) - implementation(libs.androidXAnnotation) + compileOnly(libs.kotlinBuildscript) compileOnly(libs.detektApi) testImplementation(libs.bundles.jUnit5) testImplementation(libs.bundles.testTools) testImplementation(libs.detektTest) - testImplementation(libs.robolectric) } datadogBuild { - applyKotlinConfig() + applyKotlinConfig( + kotlinApiVersion = KotlinVersion.KOTLIN_2_2, + kotlinLanguageVersion = KotlinVersion.KOTLIN_2_2 + ) applyJunitConfig() } diff --git a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/DatadogProvider.kt b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/DatadogProvider.kt index 5590504b9e..d72b16e851 100644 --- a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/DatadogProvider.kt +++ b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/DatadogProvider.kt @@ -16,31 +16,30 @@ import com.datadog.tools.detekt.rules.sdk.ThrowingInternalException import com.datadog.tools.detekt.rules.sdk.TodoWithoutTask import com.datadog.tools.detekt.rules.sdk.UnsafeCallOnNullableType import com.datadog.tools.detekt.rules.sdk.UnsafeThirdPartyFunctionCall -import io.gitlab.arturbosch.detekt.api.Config -import io.gitlab.arturbosch.detekt.api.RuleSet -import io.gitlab.arturbosch.detekt.api.RuleSetProvider +import dev.detekt.api.RuleSet +import dev.detekt.api.RuleSetProvider /** * The [RuleSetProvider] for Datadog's SDK for Android. */ class DatadogProvider : RuleSetProvider { - override val ruleSetId: String = "datadog" + override val ruleSetId: RuleSet.Id = RuleSet.Id("datadog") - override fun instance(config: Config): RuleSet { + override fun instance(): RuleSet { return RuleSet( ruleSetId, listOf( - CheckInternal(), - InvalidStringFormat(), - PackageNameVisibility(config), - PreferTimeProvider(config), - RequireInternal(), - ThreadSafety(config), - ThrowingInternalException(), - TodoWithoutTask(config), - UnsafeCallOnNullableType(), - UnsafeThirdPartyFunctionCall(config) + ::CheckInternal, + ::InvalidStringFormat, + ::PackageNameVisibility, + ::PreferTimeProvider, + ::RequireInternal, + ::ThreadSafety, + ::ThrowingInternalException, + ::TodoWithoutTask, + ::UnsafeCallOnNullableType, + ::UnsafeThirdPartyFunctionCall ) ) } diff --git a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/DatadogTestPyramidProvider.kt b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/DatadogTestPyramidProvider.kt index 526aea287c..d96bcebe27 100644 --- a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/DatadogTestPyramidProvider.kt +++ b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/DatadogTestPyramidProvider.kt @@ -8,23 +8,22 @@ package com.datadog.tools.detekt import com.datadog.tools.detekt.rules.pyramid.ApiSurface import com.datadog.tools.detekt.rules.pyramid.ApiUsage -import io.gitlab.arturbosch.detekt.api.Config -import io.gitlab.arturbosch.detekt.api.RuleSet -import io.gitlab.arturbosch.detekt.api.RuleSetProvider +import dev.detekt.api.RuleSet +import dev.detekt.api.RuleSetProvider /** * The [RuleSetProvider] for Datadog's SDK for Android. */ class DatadogTestPyramidProvider : RuleSetProvider { - override val ruleSetId: String = "datadog-test-pyramid" + override val ruleSetId: RuleSet.Id = RuleSet.Id("datadog-test-pyramid") - override fun instance(config: Config): RuleSet { + override fun instance(): RuleSet { return RuleSet( ruleSetId, listOf( - ApiUsage(config), - ApiSurface(config) + ::ApiUsage, + ::ApiSurface ) ) } diff --git a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/ext/KaTypeExt.kt b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/ext/KaTypeExt.kt new file mode 100644 index 0000000000..11c80f53e6 --- /dev/null +++ b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/ext/KaTypeExt.kt @@ -0,0 +1,73 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.tools.detekt.ext + +import org.jetbrains.kotlin.analysis.api.KaSession +import org.jetbrains.kotlin.analysis.api.types.KaClassType +import org.jetbrains.kotlin.analysis.api.types.KaDefinitelyNotNullType +import org.jetbrains.kotlin.analysis.api.types.KaFlexibleType +import org.jetbrains.kotlin.analysis.api.types.KaStarTypeProjection +import org.jetbrains.kotlin.analysis.api.types.KaType +import org.jetbrains.kotlin.analysis.api.types.KaTypeArgumentWithVariance +import org.jetbrains.kotlin.analysis.api.types.KaTypeParameterType + +internal fun KaSession.fqTypeName( + type: KaType, + treatGenericAsSuper: Boolean = true, + includeTypeArguments: Boolean = true +): String { + val baseName = when (type) { + // types from java are flexible because the Kotlin Compiler doesn't know whether + // they're nullable or not. Use the lower bound name and report the nullability below + is KaFlexibleType -> fqTypeName(type.lowerBound, treatGenericAsSuper, includeTypeArguments) + .removeSuffix("?") + + is KaDefinitelyNotNullType -> fqTypeName(type.original, treatGenericAsSuper, includeTypeArguments) + .removeSuffix("?") + + is KaTypeParameterType -> if (treatGenericAsSuper) { + // Treat generic types as their closest supertype (usually Any) + type.symbol.upperBounds.firstOrNull()?.fqClassName() ?: "kotlin.Any" + } else { + type.name.asString() + } + + is KaClassType -> { + val fqName = type.classId.asSingleFqName().asString() + if (includeTypeArguments && type.typeArguments.isNotEmpty()) { + type.typeArguments.joinToString(", ", prefix = "$fqName<", postfix = ">") { + when (it) { + is KaStarTypeProjection -> "*" + is KaTypeArgumentWithVariance -> fqTypeName(it.type, treatGenericAsSuper) + } + } + } else { + fqName + } + } + + else -> { + println("Unable to get fqName for ${type.javaClass} $type") + return UNKNOWN_TYPE + } + } + return if (type.isNullable) "$baseName?" else baseName +} + +/** + * @return the fully qualified name of the class (without type arguments nor nullability) + */ +internal fun KaType.fqClassName(): String? { + return when (this) { + is KaFlexibleType -> lowerBound.fqClassName() + is KaDefinitelyNotNullType -> original.fqClassName() + is KaClassType -> classId.asSingleFqName().asString() + else -> null + } +} + +private const val UNKNOWN_TYPE = "UNKNOWN" diff --git a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/ext/KotlinTypeExt.kt b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/ext/KotlinTypeExt.kt deleted file mode 100644 index 62d03b632b..0000000000 --- a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/ext/KotlinTypeExt.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. - * This product includes software developed at Datadog (https://www.datadoghq.com/). - * Copyright 2016-Present Datadog, Inc. - */ - -package com.datadog.tools.detekt.ext - -import org.jetbrains.kotlin.descriptors.buildPossiblyInnerType -import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull -import org.jetbrains.kotlin.types.KotlinType -import org.jetbrains.kotlin.types.checker.SimpleClassicTypeSystemContext.getArguments -import org.jetbrains.kotlin.types.isNullable -import org.jetbrains.kotlin.types.typeUtil.supertypes - -internal fun KotlinType.fqTypeName( - treatGenericAsSuper: Boolean = true, - includeTypeArguments: Boolean = true -): String { - val descriptor = if ((constructor.toString().length == 1) && treatGenericAsSuper) { - // Treat generic types as their closest supertype (usually Any) - supertypes().firstOrNull()?.buildPossiblyInnerType()?.classifierDescriptor - } else { - buildPossiblyInnerType()?.classifierDescriptor - } - val fqName = descriptor?.fqNameOrNull()?.asString() - if (fqName == null) { - println("Unable to get fqName for ${this.javaClass} $this") - return "UNKNOWN" - } - val arguments = if (getArguments().isNotEmpty()) { - if (includeTypeArguments) { - arguments.joinToString(", ", prefix = "<", postfix = ">") { - if (it.isStarProjection) "*" else it.type.fqTypeName(treatGenericAsSuper) - } - } else { - "" - } - } else { - "" - } - return if (isNullable()) { - "$fqName$arguments?" - } else { - fqName + arguments - } -} diff --git a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/ext/ReceiverExt.kt b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/ext/ReceiverExt.kt index 017d78c109..76fdc8ab9f 100644 --- a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/ext/ReceiverExt.kt +++ b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/ext/ReceiverExt.kt @@ -6,61 +6,140 @@ package com.datadog.tools.detekt.ext -import io.gitlab.arturbosch.detekt.rules.fqNameOrNull -import org.jetbrains.kotlin.resolve.BindingContext -import org.jetbrains.kotlin.resolve.calls.util.getType -import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull -import org.jetbrains.kotlin.resolve.scopes.receivers.ClassQualifier -import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver -import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver -import org.jetbrains.kotlin.resolve.scopes.receivers.PackageQualifier -import org.jetbrains.kotlin.resolve.scopes.receivers.Receiver -import org.jetbrains.kotlin.types.FlexibleType -import org.jetbrains.kotlin.types.lowerIfFlexible -import org.jetbrains.kotlin.types.typeUtil.makeNotNullable +import org.jetbrains.kotlin.analysis.api.KaSession +import org.jetbrains.kotlin.analysis.api.resolution.KaCallableMemberCall +import org.jetbrains.kotlin.analysis.api.resolution.KaImplicitReceiverValue +import org.jetbrains.kotlin.analysis.api.resolution.KaSmartCastedReceiverValue +import org.jetbrains.kotlin.analysis.api.symbols.KaCallableSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KaClassLikeSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KaNamedFunctionSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KaPackageSymbol +import org.jetbrains.kotlin.analysis.api.types.KaClassType +import org.jetbrains.kotlin.analysis.api.types.KaIntersectionType +import org.jetbrains.kotlin.analysis.api.types.KaType +import org.jetbrains.kotlin.idea.references.KtReference +import org.jetbrains.kotlin.psi.KtCallExpression +import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.psi.KtDotQualifiedExpression +import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.psi.KtNameReferenceExpression +import org.jetbrains.kotlin.psi.KtQualifiedExpression +import org.jetbrains.kotlin.psi.KtSuperExpression +import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType -internal fun Receiver.type( - bindingContext: BindingContext, +/** + * The explicit receiver of a call (e.g. `foo` in `foo.bar()`). + */ +internal sealed class ExplicitReceiver { + + abstract val typeName: String? + + /** + * A class, object or package used as a receiver (e.g. `String` in `String.format()`). + */ + data class Qualifier(override val typeName: String) : ExplicitReceiver() + + /** + * An expression used as a receiver (e.g. `"%s"` in `"%s".format()`). + */ + data class Expression(val expression: KtExpression, override val typeName: String?) : ExplicitReceiver() +} + +internal fun KtCallExpression.explicitReceiverExpression(): KtExpression? { + val qualifiedExpression = parent as? KtQualifiedExpression ?: return null + return if (qualifiedExpression.selectorExpression == this) qualifiedExpression.receiverExpression else null +} + +/** + * @param calleeSymbol the called function, used to pick the relevant type when the receiver has several + * (e.g. a smart cast or `super`) + */ +internal fun KaSession.explicitReceiver( + expression: KtCallExpression, + calleeSymbol: KaCallableSymbol, treatGenericAsSuper: Boolean, includeTypeArguments: Boolean -): String? { - return when (this) { - is ExpressionReceiver -> { - val resolvedType = expression.getType(bindingContext) - if (resolvedType is FlexibleType) { - // types from java are flexible because the Kotlin Compiler doesn't know whether - // they're nullable or not. Using the lowerBound makes it nonNullable - resolvedType.lowerIfFlexible() - .makeNotNullable() - .fqTypeName( - treatGenericAsSuper = treatGenericAsSuper, - includeTypeArguments = includeTypeArguments - ) - } else { - // Convert all types to nonNullable - resolvedType?.makeNotNullable() - ?.fqTypeName( - treatGenericAsSuper = treatGenericAsSuper, - includeTypeArguments = includeTypeArguments - ) - } +): ExplicitReceiver? { + val receiverExpression = expression.explicitReceiverExpression() ?: return null + val qualifierName = qualifierName(receiverExpression) + return if (qualifierName != null) { + ExplicitReceiver.Qualifier(qualifierName) + } else { + val candidateTypes = if (receiverExpression is KtSuperExpression) { + receiverExpression.getStrictParentOfType()?.classSymbol?.superTypes.orEmpty() + } else { + receiverExpression.expressionType?.let { type -> + (type as? KaIntersectionType)?.conjuncts ?: listOf(type) + }.orEmpty() } + // Convert all types to nonNullable + val typeName = selectReceiverType(candidateTypes, calleeSymbol) + ?.lowerBoundIfFlexible() + ?.withNullability(false) + ?.let { fqTypeName(it, treatGenericAsSuper, includeTypeArguments) } + ExplicitReceiver.Expression(receiverExpression, typeName) + } +} - is ClassQualifier -> { - descriptor.fqNameOrNull()?.toString() - } +/** + * @return the type of the receiver of the call, either explicit or implicit (e.g. `this`) + */ +internal fun KaSession.receiverTypeName( + expression: KtCallExpression, + call: KaCallableMemberCall<*, *>, + treatGenericAsSuper: Boolean, + includeTypeArguments: Boolean +): String? { + val calleeSymbol = call.partiallyAppliedSymbol.signature.symbol + val explicitReceiver = explicitReceiver(expression, calleeSymbol, treatGenericAsSuper, includeTypeArguments) + if (explicitReceiver != null) { + return explicitReceiver.typeName + } - is ImplicitReceiver -> { - type.fqNameOrNull()?.toString() - } + if ((calleeSymbol as? KaNamedFunctionSymbol)?.isStatic == true) { + // A static member called without qualifier (e.g. statically imported or inherited) belongs to its class + return calleeSymbol.callableId?.classId?.asSingleFqName()?.asString() + } - is PackageQualifier -> { - descriptor.fqName.asString() - } + val symbol = call.partiallyAppliedSymbol + val implicitReceiver = listOf(symbol.extensionReceiver, symbol.dispatchReceiver) + .map { if (it is KaSmartCastedReceiverValue) it.original else it } + .firstOrNull { it is KaImplicitReceiverValue } + return implicitReceiver?.type?.fqClassName() +} + +/** + * Picks the type declaring the called function among the possible types of a receiver, + * e.g. `Future` for `task.get()` when `task` is a `Runnable` smart cast to a `Future`. + */ +private fun KaSession.selectReceiverType(candidateTypes: List, calleeSymbol: KaCallableSymbol): KaType? { + if (candidateTypes.size <= 1) return candidateTypes.firstOrNull() + val ownerClassId = calleeSymbol.callableId?.classId + ?: (calleeSymbol.receiverParameter?.returnType?.lowerBoundIfFlexible() as? KaClassType)?.classId + val classTypes = candidateTypes.filterIsInstance() + return classTypes.firstOrNull { type -> + ownerClassId != null && + (listOf(type) + type.allSupertypes).any { (it as? KaClassType)?.classId == ownerClassId } + } ?: classTypes.firstOrNull() ?: candidateTypes.first() +} + +private fun KaSession.qualifierName(receiverExpression: KtExpression): String? { + val nameExpression = when (receiverExpression) { + is KtNameReferenceExpression -> receiverExpression + is KtDotQualifiedExpression -> receiverExpression.selectorExpression as? KtNameReferenceExpression + else -> null + } ?: return null - else -> { - println("DD: Unknown receiver type ${this.javaClass}") - null + val reference = nameExpression.references.filterIsInstance().firstOrNull() ?: return null + return when (val symbol = reference.resolveToSymbol()) { + is KaClassLikeSymbol -> { + val classId = symbol.classId + // `String.format()` references the companion object implicitly, report the class itself + val qualifierClassId = if (reference.isImplicitReferenceToCompanion()) classId?.outerClassId else classId + qualifierClassId?.asSingleFqName()?.asString() } + + is KaPackageSymbol -> symbol.fqName.asString() + else -> null } } diff --git a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/AbstractCallExpressionRule.kt b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/AbstractCallExpressionRule.kt index 067030968a..31b5eb35af 100644 --- a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/AbstractCallExpressionRule.kt +++ b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/AbstractCallExpressionRule.kt @@ -7,19 +7,23 @@ package com.datadog.tools.detekt.rules import com.datadog.tools.detekt.ext.fqTypeName -import com.datadog.tools.detekt.ext.type -import io.gitlab.arturbosch.detekt.api.Config -import org.jetbrains.kotlin.descriptors.CallableDescriptor -import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor -import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor -import org.jetbrains.kotlin.descriptors.containingPackage +import com.datadog.tools.detekt.ext.receiverTypeName +import dev.detekt.api.Config +import dev.detekt.api.RequiresAnalysisApi +import org.jetbrains.kotlin.analysis.api.KaSession +import org.jetbrains.kotlin.analysis.api.analyze +import org.jetbrains.kotlin.analysis.api.resolution.KaFunctionCall +import org.jetbrains.kotlin.analysis.api.resolution.KaSimpleFunctionCall +import org.jetbrains.kotlin.analysis.api.resolution.successfulFunctionCallOrNull +import org.jetbrains.kotlin.analysis.api.signatures.KaVariableSignature +import org.jetbrains.kotlin.analysis.api.symbols.KaConstructorSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KaValueParameterSymbol +import org.jetbrains.kotlin.analysis.api.types.KaClassType +import org.jetbrains.kotlin.analysis.api.types.KaFlexibleType +import org.jetbrains.kotlin.analysis.api.types.KaType +import org.jetbrains.kotlin.name.StandardClassIds import org.jetbrains.kotlin.psi.KtCallExpression -import org.jetbrains.kotlin.resolve.BindingContext -import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall -import org.jetbrains.kotlin.resolve.calls.util.getCall -import org.jetbrains.kotlin.resolve.calls.util.getImplicitReceiverValue -import org.jetbrains.kotlin.resolve.calls.util.getResolvedCall -import org.jetbrains.kotlin.resolve.calls.util.getType +import org.jetbrains.kotlin.util.OperatorNameConventions /** * An abstract Detekt rule resolving function calls. @@ -27,7 +31,8 @@ import org.jetbrains.kotlin.resolve.calls.util.getType * This class resolves the types involved (receiver/parent class, function name, argument types) * and delegates the handling of the call to the child implementation. * - * @param ruleSetConfig the detekt ruleSet configuration + * @param config the detekt rule configuration + * @param description the description of the rule * @param simplifyLocalTypes if a call in class `com.example.foo.A` uses type `com.example.foo.B`, * refer to the latter as `B` * @param treatGenericAsSuper if a call uses a generic (e.g. ), @@ -36,11 +41,12 @@ import org.jetbrains.kotlin.resolve.calls.util.getType * (e.g. if false, the type `List` would only be reported as `List`) */ abstract class AbstractCallExpressionRule( - ruleSetConfig: Config = Config.empty, + config: Config, + description: String, private val simplifyLocalTypes: Boolean = false, private val treatGenericAsSuper: Boolean = true, private val includeTypeArguments: Boolean = true -) : AbstractTypedRule(ruleSetConfig) { +) : AbstractTypedRule(config, description), RequiresAnalysisApi { /** * A representation for a function with resolved types. @@ -60,69 +66,86 @@ abstract class AbstractCallExpressionRule( // region Rule - @Suppress("ReturnCount") override fun visitCallExpression(expression: KtCallExpression) { super.visitCallExpression(expression) - if (bindingContext == BindingContext.EMPTY) { - return + + val resolvedFunctionCall = analyze(expression) { resolveFunctionCall(expression) } + if (resolvedFunctionCall != null) { + visitResolvedFunctionCall(expression, resolvedFunctionCall) } + } - val resolvedCall = expression.getResolvedCall(bindingContext) - if (resolvedCall == null) { - println("Cannot resolve call for ${expression.getCall(bindingContext)}. Is classpath complete?") - return + @Suppress("ReturnCount") + private fun KaSession.resolveFunctionCall(expression: KtCallExpression): ResolvedFunCall? { + val call = expression.resolveToCall()?.successfulFunctionCallOrNull() + if (call == null) { + println("Cannot resolve call for ${expression.text}. Is classpath complete?") + return null + } + val expressionType = expression.expressionType ?: return null + val returnType = fqTypeName(expressionType, treatGenericAsSuper, includeTypeArguments) + + // Calling a value with a function type (e.g. `callback()`) is an implicit call to its `invoke` function + val isImplicitInvoke = (call as? KaSimpleFunctionCall)?.isImplicitInvoke == true + val receiverType = if (isImplicitInvoke) { + call.partiallyAppliedSymbol.dispatchReceiver?.type + ?.let { + fqTypeName( + it.lowerBoundIfFlexible().withNullability(false), + treatGenericAsSuper, + includeTypeArguments + ) + } + } else { + receiverTypeName(expression, call, treatGenericAsSuper, includeTypeArguments) } - val call = resolvedCall.call - val returnType = expression.getType(bindingContext) - ?.fqTypeName(treatGenericAsSuper, includeTypeArguments) - ?: return - - val receiverType = listOf( - call.explicitReceiver, - call.dispatchReceiver, - resolvedCall.getImplicitReceiverValue() - ) - .firstNotNullOfOrNull { - it?.type(bindingContext, treatGenericAsSuper, includeTypeArguments) - } - val callDescriptor = resolvedCall.candidateDescriptor - val (containerFqName, functionName) = if (callDescriptor is ClassConstructorDescriptor) { + val symbol = call.partiallyAppliedSymbol.signature.symbol + // Calls to a typealias constructor (e.g. `HashMap()`) are reported as functions in the typealias' package + val typeAlias = (expressionType as? KaClassType)?.abbreviation + val containingPackage = if (symbol is KaConstructorSymbol) { + (typeAlias?.classId ?: symbol.containingClassId)?.packageFqName?.asString().orEmpty() + } else { + symbol.callableId?.packageName?.asString().orEmpty() + } + val (containerFqName, functionName) = if (symbol is KaConstructorSymbol && typeAlias == null) { returnType to "constructor" } else { - val calleeName = call.calleeExpression?.node?.text ?: "UNKNOWNFUN" - val callContainingPackage = callDescriptor.containingPackage()?.toString().orEmpty() - if (receiverType == null) { - callContainingPackage to calleeName + val calleeName = if (isImplicitInvoke) { + OperatorNameConventions.INVOKE.asString() } else { - "$receiverType" to calleeName + expression.calleeExpression?.node?.text ?: "UNKNOWNFUN" } + (receiverType ?: containingPackage) to calleeName } - val arguments = resolveParameterTypes(resolvedCall, containerFqName) - val resolvedFunctionCall = ResolvedFunCall( + val arguments = resolveParameterTypes(call, containerFqName) + return ResolvedFunCall( call = "$containerFqName.$functionName(${arguments.joinToString(", ")})", containerFqName = containerFqName, functionName = functionName, - containingPackage = callDescriptor.containingPackage()?.toString().orEmpty(), + containingPackage = containingPackage, arguments = arguments ) - - visitResolvedFunctionCall(expression, resolvedFunctionCall) } - private fun resolveParameterTypes( - resolvedCall: ResolvedCall, + private fun KaSession.resolveParameterTypes( + call: KaFunctionCall<*>, containerFqName: String ): List { - return resolvedCall.valueArguments - // Ensure the arguments when named are in original order - .toSortedMap { p0, p1 -> (p0?.index ?: Int.MAX_VALUE) - (p1?.index ?: -1) } - .map { it.key.parameterType(containerFqName) } + return call.partiallyAppliedSymbol.signature.valueParameters + .map { parameterType(it, containerFqName) } } - private fun ValueParameterDescriptor.parameterType(containerFqName: String): String { - val fullType = type.fqTypeName(treatGenericAsSuper, includeTypeArguments) + private fun KaSession.parameterType( + parameter: KaVariableSignature, + containerFqName: String + ): String { + val fullType = if (parameter.symbol.isVararg) { + varargTypeName(declaredElementType = parameter.symbol.returnType, elementType = parameter.returnType) + } else { + fqTypeName(parameter.returnType, treatGenericAsSuper, includeTypeArguments) + } val (nonNullType, suffix) = if (fullType.endsWith('?')) { fullType.substringBeforeLast('?') to "?" } else { @@ -168,6 +191,32 @@ abstract class AbstractCallExpressionRule( // endregion + /** + * The type of a vararg parameter is its element type, wrap it in the matching array type. + * Only parameters declared with a primitive type use a primitive array (e.g. `vararg Int` is an `IntArray`), + * a generic parameter is always an `Array` (e.g. `vararg T` is an `Array`, even when `T` is `Int`). + */ + private fun KaSession.varargTypeName(declaredElementType: KaType, elementType: KaType): String { + val declaredClassId = (declaredElementType.lowerBoundIfFlexible() as? KaClassType)?.classId + if (declaredClassId == StandardClassIds.Array || + declaredClassId in StandardClassIds.primitiveArrayTypeByElementType.values + ) { + // already an array type + return fqTypeName(elementType, treatGenericAsSuper, includeTypeArguments) + } + val primitiveArrayClassId = StandardClassIds.primitiveArrayTypeByElementType[declaredClassId] + val arrayTypeName = if (primitiveArrayClassId != null && !declaredElementType.isMarkedNullable) { + primitiveArrayClassId.asSingleFqName().asString() + } else if (includeTypeArguments) { + val elementTypeName = fqTypeName(elementType, treatGenericAsSuper) + "${StandardClassIds.Array.asSingleFqName()}<$elementTypeName>" + } else { + StandardClassIds.Array.asSingleFqName().asString() + } + // A Java vararg is a platform array, which can be null + return if (declaredElementType is KaFlexibleType) "$arrayTypeName?" else arrayTypeName + } + companion object { private val containerFqNameRegex = Regex("^([a-z0-9-]+(\\.[a-z0-9-]+)*)((\\.[A-Z][a-zA-Z0-9-]+)*)$") } diff --git a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/AbstractTypedRule.kt b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/AbstractTypedRule.kt index 92f165275f..7df8aa7d2b 100644 --- a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/AbstractTypedRule.kt +++ b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/AbstractTypedRule.kt @@ -6,21 +6,22 @@ package com.datadog.tools.detekt.rules -import io.gitlab.arturbosch.detekt.api.Config -import io.gitlab.arturbosch.detekt.api.Rule +import dev.detekt.api.Config +import dev.detekt.api.Rule import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtImportDirective import org.jetbrains.kotlin.psi.KtPackageDirective -import org.jetbrains.kotlin.resolve.BindingContext /** * An abstract Detekt rule keeping track of imports to resolve types found in the code. * - * @param ruleSetConfig the detekt ruleSet configuration + * @param config the detekt rule configuration + * @param description the description of the rule */ abstract class AbstractTypedRule( - ruleSetConfig: Config = Config.empty -) : Rule(ruleSetConfig) { + config: Config, + description: String +) : Rule(config, description) { private val imports = mutableMapOf() private var packageName = "" @@ -28,9 +29,6 @@ abstract class AbstractTypedRule( // region Rule override fun visitKtFile(file: KtFile) { - if (bindingContext == BindingContext.EMPTY) { - println("\nMissing BindingContext when checking file:${file.virtualFilePath}") - } imports.clear() super.visitKtFile(file) } diff --git a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/pyramid/ApiSurface.kt b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/pyramid/ApiSurface.kt index b30e1fc1a7..6c1a8f10e1 100644 --- a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/pyramid/ApiSurface.kt +++ b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/pyramid/ApiSurface.kt @@ -7,13 +7,8 @@ package com.datadog.tools.detekt.rules.pyramid import com.datadog.tools.detekt.rules.AbstractTypedRule -import io.gitlab.arturbosch.detekt.api.Config -import io.gitlab.arturbosch.detekt.api.Debt -import io.gitlab.arturbosch.detekt.api.Issue -import io.gitlab.arturbosch.detekt.api.Severity -import io.gitlab.arturbosch.detekt.api.config -import io.gitlab.arturbosch.detekt.api.internal.RequiresTypeResolution -import io.gitlab.arturbosch.detekt.rules.isOverride +import dev.detekt.api.Config +import dev.detekt.api.config import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtFunctionType @@ -33,10 +28,9 @@ import java.io.File /** * @active */ -@RequiresTypeResolution class ApiSurface( - ruleSetConfig: Config -) : AbstractTypedRule(ruleSetConfig) { + config: Config +) : AbstractTypedRule(config, "This rule reports api surface.") { private val outputFileName: String by config(defaultValue = "apiSurface.log") private val outputFile: File by lazy { @@ -48,13 +42,6 @@ class ApiSurface( // region Rule - override val issue: Issue = Issue( - javaClass.simpleName, - Severity.Maintainability, - "This rule reports api surface.", - Debt.FIVE_MINS - ) - override fun visitObjectDeclaration(declaration: KtObjectDeclaration) { val hasIgnoredKeyword = ignoredKeywords.any { declaration.hasModifier(it) } val isIgnoredClass = declaration.fqName?.asString() in ignoredClasses @@ -114,7 +101,7 @@ class ApiSurface( val hasIgnoredAnnotation = function.annotationEntries.any { it.shortName?.asString()?.resolveFullType() in ignoredAnnotations } - val isOverride = function.isOverride() + val isOverride = function.hasModifier(KtTokens.OVERRIDE_KEYWORD) @Suppress("ComplexCondition") if (hasIgnoredKeyword || hasIgnoredName || hasIgnoredAnnotation || isOverride) { diff --git a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/pyramid/ApiUsage.kt b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/pyramid/ApiUsage.kt index bb51b0945d..895f0beb00 100644 --- a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/pyramid/ApiUsage.kt +++ b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/pyramid/ApiUsage.kt @@ -7,12 +7,8 @@ package com.datadog.tools.detekt.rules.pyramid import com.datadog.tools.detekt.rules.AbstractCallExpressionRule -import io.gitlab.arturbosch.detekt.api.Config -import io.gitlab.arturbosch.detekt.api.Debt -import io.gitlab.arturbosch.detekt.api.Issue -import io.gitlab.arturbosch.detekt.api.Severity -import io.gitlab.arturbosch.detekt.api.config -import io.gitlab.arturbosch.detekt.api.internal.RequiresTypeResolution +import dev.detekt.api.Config +import dev.detekt.api.config import org.jetbrains.kotlin.psi.KtCallExpression import org.jetbrains.kotlin.psi.KtNamedFunction import java.io.File @@ -20,10 +16,14 @@ import java.io.File /** * @active */ -@RequiresTypeResolution class ApiUsage( config: Config -) : AbstractCallExpressionRule(config, simplifyLocalTypes = true, includeTypeArguments = false) { +) : AbstractCallExpressionRule( + config, + "This rule reports api usages.", + simplifyLocalTypes = true, + includeTypeArguments = false +) { private val outputFileName: String by config(defaultValue = "apiUsage.log") private val outputFile: File by lazy { @@ -36,13 +36,6 @@ class ApiUsage( // region Rule - override val issue: Issue = Issue( - javaClass.simpleName, - Severity.Maintainability, - "This rule reports api usages.", - Debt.FIVE_MINS - ) - override fun visitNamedFunction(function: KtNamedFunction) { val annotations = function.annotationEntries.mapNotNull { it.shortName?.asString()?.resolveFullType() diff --git a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/CheckInternal.kt b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/CheckInternal.kt index 977849a542..0bfa54f469 100644 --- a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/CheckInternal.kt +++ b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/CheckInternal.kt @@ -7,26 +7,19 @@ package com.datadog.tools.detekt.rules.sdk import com.datadog.tools.detekt.ext.isContainingEntryPointPublic -import io.gitlab.arturbosch.detekt.api.CodeSmell -import io.gitlab.arturbosch.detekt.api.Debt -import io.gitlab.arturbosch.detekt.api.Entity -import io.gitlab.arturbosch.detekt.api.Issue -import io.gitlab.arturbosch.detekt.api.Rule -import io.gitlab.arturbosch.detekt.api.Severity +import dev.detekt.api.Config +import dev.detekt.api.Entity +import dev.detekt.api.Finding +import dev.detekt.api.Rule import org.jetbrains.kotlin.psi.KtCallExpression /** * A rule to detekt `check`, `checkNotNull` calls. * @active */ -class CheckInternal : Rule() { - - override val issue: Issue = Issue( - javaClass.simpleName, - Severity.Defect, - "This rule reports when an exception is thrown.", - Debt.TWENTY_MINS - ) +class CheckInternal( + config: Config +) : Rule(config, "This rule reports when an exception is thrown.") { override fun visitCallExpression(expression: KtCallExpression) { val callee = expression.calleeExpression @@ -37,10 +30,9 @@ class CheckInternal : Rule() { !expression.isContainingEntryPointPublic() ) { report( - CodeSmell( - issue, + Finding( Entity.from(expression), - message = "A check is called from an internal or private part of the code." + "A check is called from an internal or private part of the code." ) ) } diff --git a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/InvalidStringFormat.kt b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/InvalidStringFormat.kt index 5993fcb3db..986757b238 100644 --- a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/InvalidStringFormat.kt +++ b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/InvalidStringFormat.kt @@ -6,32 +6,27 @@ package com.datadog.tools.detekt.rules.sdk +import com.datadog.tools.detekt.ext.ExplicitReceiver +import com.datadog.tools.detekt.ext.explicitReceiver import com.datadog.tools.detekt.ext.fqTypeName -import com.datadog.tools.detekt.ext.type -import io.gitlab.arturbosch.detekt.api.CodeSmell -import io.gitlab.arturbosch.detekt.api.Debt -import io.gitlab.arturbosch.detekt.api.Entity -import io.gitlab.arturbosch.detekt.api.Issue -import io.gitlab.arturbosch.detekt.api.Rule -import io.gitlab.arturbosch.detekt.api.Severity -import org.jetbrains.kotlin.descriptors.CallableDescriptor -import org.jetbrains.kotlin.descriptors.VariableDescriptor +import dev.detekt.api.Config +import dev.detekt.api.Entity +import dev.detekt.api.Finding +import dev.detekt.api.RequiresAnalysisApi +import dev.detekt.api.Rule +import org.jetbrains.kotlin.analysis.api.KaSession +import org.jetbrains.kotlin.analysis.api.analyze +import org.jetbrains.kotlin.analysis.api.resolution.KaFunctionCall +import org.jetbrains.kotlin.analysis.api.resolution.successfulFunctionCallOrNull +import org.jetbrains.kotlin.analysis.api.symbols.KaVariableSymbol +import org.jetbrains.kotlin.idea.references.KtReference import org.jetbrains.kotlin.psi.KtCallExpression import org.jetbrains.kotlin.psi.KtDotQualifiedExpression import org.jetbrains.kotlin.psi.KtExpression import org.jetbrains.kotlin.psi.KtReferenceExpression import org.jetbrains.kotlin.psi.KtStringTemplateExpression +import org.jetbrains.kotlin.psi.KtVariableDeclaration import org.jetbrains.kotlin.psi.psiUtil.children -import org.jetbrains.kotlin.resolve.BindingContext -import org.jetbrains.kotlin.resolve.bindingContextUtil.getReferenceTargets -import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall -import org.jetbrains.kotlin.resolve.calls.model.VarargValueArgument -import org.jetbrains.kotlin.resolve.calls.util.getResolvedCall -import org.jetbrains.kotlin.resolve.calls.util.getType -import org.jetbrains.kotlin.resolve.scopes.receivers.ClassQualifier -import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver -import org.jetbrains.kotlin.types.lowerIfFlexible -import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull import java.util.Locale /** @@ -40,103 +35,106 @@ import java.util.Locale * * @active */ -class InvalidStringFormat : Rule() { +class InvalidStringFormat( + config: Config +) : Rule(config, "This rule reports when a String format pattern does not match the provided arguments."), + RequiresAnalysisApi { // region Rule - override val issue: Issue = Issue( - javaClass.simpleName, - Severity.Defect, - "This rule reports when a String format pattern does not match the provided arguments.", - Debt.TWENTY_MINS - ) - - @Suppress("ReturnCount") override fun visitCallExpression(expression: KtCallExpression) { super.visitCallExpression(expression) - if (bindingContext == BindingContext.EMPTY) { - return - } - val resolvedCall = expression.getResolvedCall(bindingContext) ?: return - val call = resolvedCall.call + val formatCall = analyze(expression) { resolveFormatCall(expression) } ?: return + + checkFormat(expression, formatCall.formatString, formatCall.formatArgs) + } + + // endregion + + // region Internal + + private class FormatCall( + val formatString: String?, + val formatArgs: List + ) + + @Suppress("ReturnCount") + private fun KaSession.resolveFormatCall(expression: KtCallExpression): FormatCall? { + val call = expression.resolveToCall()?.successfulFunctionCallOrNull() ?: return null // check receiver type - val explicitReceiver = call.explicitReceiver - val receiverType = explicitReceiver?.type( - bindingContext, + val explicitReceiver = explicitReceiver( + expression, + call.partiallyAppliedSymbol.signature.symbol, treatGenericAsSuper = true, includeTypeArguments = true ) - if (receiverType != STRING_CLASS) return + if (explicitReceiver?.typeName != STRING_CLASS) return null // check method call - val calleeExpression = call.calleeExpression?.node?.text - if (calleeExpression != FORMAT_METHOD) return - - val formatString = extractFormatString(resolvedCall) - val formatArgs = extractFormatArgs(resolvedCall) + val calleeExpression = expression.calleeExpression?.node?.text + if (calleeExpression != FORMAT_METHOD) return null - checkFormat(expression, formatString, formatArgs) + return FormatCall( + formatString = extractFormatString(expression, call, explicitReceiver), + formatArgs = extractFormatArgs(expression, call, explicitReceiver) + ) } - // endregion - - // region Internal - - private fun extractFormatString( - resolvedCall: ResolvedCall + private fun KaSession.extractFormatString( + expression: KtCallExpression, + call: KaFunctionCall<*>, + explicitReceiver: ExplicitReceiver ): String? { - val call = resolvedCall.call - val stringExpression = when (val explicitReceiver = call.explicitReceiver) { - is ClassQualifier -> { + val stringExpression = when (explicitReceiver) { + is ExplicitReceiver.Qualifier -> { // String.format(…) - val arguments = resolvedCall.valueArguments.toList().firstOrNull { - it.first.type.fqTypeName() == STRING_CLASS - }?.second?.arguments - if (arguments != null && arguments.size == 1) { - arguments.first().getArgumentExpression() + val formatParameter = call.partiallyAppliedSymbol.signature.valueParameters.firstOrNull { + fqTypeName(it.returnType) == STRING_CLASS + } + val arguments = call.argumentMapping.filterValues { it.symbol == formatParameter?.symbol }.keys + if (formatParameter != null && arguments.size == 1) { + arguments.first() } else { null } } - is ExpressionReceiver -> { + is ExplicitReceiver.Expression -> { // "…".format(…) explicitReceiver.expression } - - else -> { - println("Unknown receiver:$explicitReceiver") - null - } } - val expression = if (stringExpression is KtDotQualifiedExpression) { + val resolvedExpression = if (stringExpression is KtDotQualifiedExpression) { // Referencing a constant from another class stringExpression.selectorExpression } else { stringExpression } - return resolveStringExpression(expression) + return resolveStringExpression(resolvedExpression) } - private fun resolveStringExpression(stringExpression: KtExpression?): String? { + private fun KaSession.resolveStringExpression(stringExpression: KtExpression?): String? { return if (stringExpression is KtStringTemplateExpression) { - stringExpression.node - .children() - .filter { it.text != "\"" } - .joinToString("") { it.text } + stringExpression.evaluate()?.value as? String + ?: stringExpression.node + .children() + .filter { it.text != "\"" } + .joinToString("") { it.text } } else if (stringExpression is KtReferenceExpression) { - val referenceTarget = stringExpression.getReferenceTargets(bindingContext) - .firstIsInstanceOrNull() - - if (referenceTarget is VariableDescriptor && !referenceTarget.isVar) { - val compileTimeInitializer = referenceTarget.compileTimeInitializer - if (compileTimeInitializer != null) { - compileTimeInitializer.value as? String + val referenceTarget = stringExpression.references + .filterIsInstance() + .firstNotNullOfOrNull { it.resolveToSymbol() as? KaVariableSymbol } + + if (referenceTarget != null && referenceTarget.isVal) { + val compileTimeValue = stringExpression.evaluate()?.value + ?: (referenceTarget.psi as? KtVariableDeclaration)?.initializer?.evaluate()?.value + if (compileTimeValue != null) { + compileTimeValue as? String } else { - println("Unable to get compileTimeInitializer for $referenceTarget") + println("Unable to get compile time value for $referenceTarget") null } } else { @@ -149,17 +147,19 @@ class InvalidStringFormat : Rule() { } } - private fun extractFormatArgs( - resolvedCall: ResolvedCall + private fun KaSession.extractFormatArgs( + expression: KtCallExpression, + call: KaFunctionCall<*>, + explicitReceiver: ExplicitReceiver ): List { - val receiver = resolvedCall.call.explicitReceiver - val formatParams = resolvedCall.valueArgumentsByIndex?.last() as? VarargValueArgument - - val rawTypes = formatParams?.arguments.orEmpty() - .map { - it.getArgumentExpression()?.getType(bindingContext)?.lowerIfFlexible()?.fqTypeName() - } - return if (receiver is ClassQualifier && rawTypes.firstOrNull() == LOCALE_CLASS) { + val formatParameter = call.partiallyAppliedSymbol.signature.valueParameters.lastOrNull() + ?.takeIf { it.symbol.isVararg } + + val rawTypes = expression.valueArguments + .mapNotNull { it.getArgumentExpression() } + .filter { formatParameter != null && call.argumentMapping[it]?.symbol == formatParameter.symbol } + .map { argument -> argument.expressionType?.lowerBoundIfFlexible()?.let { fqTypeName(it) } } + return if (explicitReceiver is ExplicitReceiver.Qualifier && rawTypes.firstOrNull() == LOCALE_CLASS) { rawTypes.drop(1) } else { rawTypes @@ -172,9 +172,9 @@ class InvalidStringFormat : Rule() { formatArgs: List ) { if (formatString == null) { - report(CodeSmell(issue, Entity.from(expression), ERROR_UNKNOWN_FORMAT_STRING)) + report(Finding(Entity.from(expression), ERROR_UNKNOWN_FORMAT_STRING)) } else if (formatArgs.isEmpty()) { - report(CodeSmell(issue, Entity.from(expression), ERROR_UNKNOWN_FORMAT_STRING)) + report(Finding(Entity.from(expression), ERROR_UNKNOWN_FORMAT_STRING)) } else { val specifiers = SPECIFIER_REGEX.findAll(formatString).toList() checkSpecifiers(expression, specifiers, formatArgs) @@ -194,7 +194,7 @@ class InvalidStringFormat : Rule() { val argIndex = (ref ?: ++indexNoRef) - 1 if (argIndex >= formatArgs.size) { val message = ERROR_INVALID_ARGUMENT_COUNT.format(Locale.US, matchResult.value) - report(CodeSmell(issue, Entity.from(expression), message)) + report(Finding(Entity.from(expression), message)) } else { val argType = formatArgs[argIndex] checkArgumentType(expression, argType, type) @@ -230,11 +230,11 @@ class InvalidStringFormat : Rule() { if (allowedTypes == null) { val message = ERROR_INVALID_ARGUMENT_TYPE + "Unknown specifier %$type." - report(CodeSmell(issue, Entity.from(expression), message)) + report(Finding(Entity.from(expression), message)) } else if (!allowedTypes.contentEquals(ANY_TYPES) && argType !in allowedTypes) { val message = ERROR_INVALID_ARGUMENT_TYPE + " Expected one of ${allowedTypes.joinToString()}; but was $argType." - report(CodeSmell(issue, Entity.from(expression), message)) + report(Finding(Entity.from(expression), message)) } } diff --git a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/PackageNameVisibility.kt b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/PackageNameVisibility.kt index f831028190..38dcab4b5a 100644 --- a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/PackageNameVisibility.kt +++ b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/PackageNameVisibility.kt @@ -7,14 +7,10 @@ package com.datadog.tools.detekt.rules.sdk import com.datadog.tools.detekt.rules.AbstractTypedRule -import io.gitlab.arturbosch.detekt.api.CodeSmell -import io.gitlab.arturbosch.detekt.api.Config -import io.gitlab.arturbosch.detekt.api.Debt -import io.gitlab.arturbosch.detekt.api.Entity -import io.gitlab.arturbosch.detekt.api.Issue -import io.gitlab.arturbosch.detekt.api.Severity -import io.gitlab.arturbosch.detekt.api.config -import io.gitlab.arturbosch.detekt.api.internal.RequiresTypeResolution +import dev.detekt.api.Config +import dev.detekt.api.Entity +import dev.detekt.api.Finding +import dev.detekt.api.config import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.KtModifierListOwner import org.jetbrains.kotlin.psi.KtNamedDeclaration @@ -28,21 +24,16 @@ import org.jetbrains.kotlin.psi.KtProperty * `com.datadog.android.*.internal.*` package name. * @active */ -@RequiresTypeResolution class PackageNameVisibility( - ruleSetConfig: Config -) : AbstractTypedRule(ruleSetConfig) { + config: Config +) : AbstractTypedRule( + config, + "This rule reports when a top level declaration's visibility doesn't match the package naming policy." +) { private val withBreakingChanges: Boolean by config(defaultValue = true) private val ignoredAnnotations: List by config(defaultValue = emptyList()) - override val issue: Issue = Issue( - javaClass.simpleName, - Severity.Warning, - "This rule reports when a top level declaration's visibility doesn't match the package naming policy.", - Debt.FIVE_MINS - ) - private var currentPackageName: String = "" private var isCurrentPackageInternal: Boolean = false private var isEscapePackage: Boolean = false @@ -63,8 +54,7 @@ class PackageNameVisibility( if (isDeclarationInternal && !isCurrentPackageInternal) { if (decl !is KtProperty) { report( - CodeSmell( - issue, + Finding( Entity.from(decl), "Type ${decl.name} is marked Internal but is not in an internal package: $currentPackageName." ) @@ -73,8 +63,7 @@ class PackageNameVisibility( } else if (isCurrentPackageInternal && !isDeclarationInternal && !isEscapePackage) { if (withBreakingChanges && !isIgnoredAnnotation) { report( - CodeSmell( - issue, + Finding( Entity.from(decl), "Type ${decl.name} is marked Public but is in an internal package: $currentPackageName." ) diff --git a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/PreferTimeProvider.kt b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/PreferTimeProvider.kt index 4eb4f3444b..d6a599bf65 100644 --- a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/PreferTimeProvider.kt +++ b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/PreferTimeProvider.kt @@ -6,14 +6,11 @@ package com.datadog.tools.detekt.rules.sdk -import io.gitlab.arturbosch.detekt.api.CodeSmell -import io.gitlab.arturbosch.detekt.api.Config -import io.gitlab.arturbosch.detekt.api.Debt -import io.gitlab.arturbosch.detekt.api.Entity -import io.gitlab.arturbosch.detekt.api.Issue -import io.gitlab.arturbosch.detekt.api.Rule -import io.gitlab.arturbosch.detekt.api.Severity -import io.gitlab.arturbosch.detekt.api.config +import dev.detekt.api.Config +import dev.detekt.api.Entity +import dev.detekt.api.Finding +import dev.detekt.api.Rule +import dev.detekt.api.config import org.jetbrains.kotlin.psi.KtCallExpression import org.jetbrains.kotlin.psi.KtDotQualifiedExpression import org.jetbrains.kotlin.psi.KtFile @@ -27,8 +24,8 @@ import org.jetbrains.kotlin.psi.KtFile * @active */ class PreferTimeProvider( - ruleSetConfig: Config = Config.empty -) : Rule(ruleSetConfig) { + config: Config +) : Rule(config, "Prefer using TimeProvider instead of static system time calls for better testability.") { private val allowedFiles: List by config(defaultValue = DEFAULT_ALLOWED_FILES) { it.map { pattern -> Regex(pattern) } @@ -36,13 +33,6 @@ class PreferTimeProvider( private var currentFileName: String = "" - override val issue: Issue = Issue( - javaClass.simpleName, - Severity.Defect, - "Prefer using TimeProvider instead of static system time calls for better testability.", - Debt.TEN_MINS - ) - override fun visitKtFile(file: KtFile) { currentFileName = file.name super.visitKtFile(file) @@ -56,8 +46,7 @@ class PreferTimeProvider( val calleeName = expression.calleeExpression?.text ?: return if (calleeName in KOTLIN_TIME_FUNCTIONS) { report( - CodeSmell( - issue, + Finding( Entity.from(expression), "Use TimeProvider instead of $calleeName() for better testability." ) @@ -74,8 +63,7 @@ class PreferTimeProvider( val detectedMethod = detectProhibitedTimeMethod(expressionText) if (detectedMethod != null) { report( - CodeSmell( - issue, + Finding( Entity.from(expression), "Use TimeProvider instead of $detectedMethod for better testability." ) diff --git a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/RequireInternal.kt b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/RequireInternal.kt index ee8234ce65..13c85660c7 100644 --- a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/RequireInternal.kt +++ b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/RequireInternal.kt @@ -7,26 +7,19 @@ package com.datadog.tools.detekt.rules.sdk import com.datadog.tools.detekt.ext.isContainingEntryPointPublic -import io.gitlab.arturbosch.detekt.api.CodeSmell -import io.gitlab.arturbosch.detekt.api.Debt -import io.gitlab.arturbosch.detekt.api.Entity -import io.gitlab.arturbosch.detekt.api.Issue -import io.gitlab.arturbosch.detekt.api.Rule -import io.gitlab.arturbosch.detekt.api.Severity +import dev.detekt.api.Config +import dev.detekt.api.Entity +import dev.detekt.api.Finding +import dev.detekt.api.Rule import org.jetbrains.kotlin.psi.KtCallExpression /** * A rule to detekt `check`, `checkNotNull` calls. * @active */ -class RequireInternal : Rule() { - - override val issue: Issue = Issue( - javaClass.simpleName, - Severity.Defect, - "This rule reports when an exception is thrown.", - Debt.TWENTY_MINS - ) +class RequireInternal( + config: Config +) : Rule(config, "This rule reports when an exception is thrown.") { override fun visitCallExpression(expression: KtCallExpression) { val callee = expression.calleeExpression @@ -34,10 +27,9 @@ class RequireInternal : Rule() { val isCheckNotNullMethod = callee?.textMatches(REQUIRE_NOT_NULL_LITERAL) == true if ((isCheckMethod || isCheckNotNullMethod) && !expression.isContainingEntryPointPublic()) { report( - CodeSmell( - issue, + Finding( Entity.from(expression), - message = "A require is called from an internal or private part of the code." + "A require is called from an internal or private part of the code." ) ) } diff --git a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/ThreadSafety.kt b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/ThreadSafety.kt index bc3c5fa36e..968b9d980a 100644 --- a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/ThreadSafety.kt +++ b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/ThreadSafety.kt @@ -6,28 +6,22 @@ package com.datadog.tools.detekt.rules.sdk +import com.datadog.tools.detekt.ext.fqClassName import com.datadog.tools.detekt.ext.fqTypeName import com.datadog.tools.detekt.rules.AbstractTypedRule -import io.gitlab.arturbosch.detekt.api.CodeSmell -import io.gitlab.arturbosch.detekt.api.Config -import io.gitlab.arturbosch.detekt.api.Debt -import io.gitlab.arturbosch.detekt.api.Entity -import io.gitlab.arturbosch.detekt.api.Issue -import io.gitlab.arturbosch.detekt.api.Severity -import io.gitlab.arturbosch.detekt.api.config -import io.gitlab.arturbosch.detekt.rules.fqNameOrNull -import org.jetbrains.kotlin.descriptors.CallableDescriptor -import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor +import dev.detekt.api.Config +import dev.detekt.api.Entity +import dev.detekt.api.Finding +import dev.detekt.api.RequiresAnalysisApi +import dev.detekt.api.config +import org.jetbrains.kotlin.analysis.api.KaSession +import org.jetbrains.kotlin.analysis.api.analyze +import org.jetbrains.kotlin.analysis.api.resolution.KaFunctionCall +import org.jetbrains.kotlin.analysis.api.resolution.successfulFunctionCallOrNull +import org.jetbrains.kotlin.analysis.api.symbols.KaCallableSymbol import org.jetbrains.kotlin.psi.KtCallExpression -import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtNameReferenceExpression import org.jetbrains.kotlin.psi.KtNamedFunction -import org.jetbrains.kotlin.resolve.BindingContext -import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall -import org.jetbrains.kotlin.resolve.calls.util.getResolvedCall -import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull -import org.jetbrains.kotlin.types.SimpleType -import org.jetbrains.kotlin.types.getAbbreviation /** * A rule to ensure thread safety is ensured. @@ -36,8 +30,9 @@ import org.jetbrains.kotlin.types.getAbbreviation * @active */ class ThreadSafety( - ruleSetConfig: Config -) : AbstractTypedRule(ruleSetConfig) { + config: Config +) : AbstractTypedRule(config, "This rule reports when a method is called from the wrong thread."), + RequiresAnalysisApi { private val workerThreadSwitchingCalls: List by config(defaultValue = emptyList()) private val mainThreadSwitchingCalls: List by config(defaultValue = emptyList()) @@ -76,21 +71,6 @@ class ThreadSafety( // region Rule - override val issue = Issue( - javaClass.simpleName, - Severity.Security, - "This rule reports when a method is called from the wrong thread.", - Debt.TWENTY_MINS - ) - - override fun visitKtFile(file: KtFile) { - if (bindingContext == BindingContext.EMPTY) { - println("Missing BindingContext when checking file:${file.virtualFilePath}") - return - } - super.visitKtFile(file) - } - override fun visitNamedFunction(function: KtNamedFunction) { val parentFunGroup: ThreadGroup = function.annotationEntries.mapNotNull { it.shortName?.asString()?.toMethodGroup() @@ -103,14 +83,19 @@ class ThreadSafety( } override fun visitCallExpression(expression: KtCallExpression) { + val resolvedCall = analyze(expression) { + expression.resolveToCall()?.successfulFunctionCallOrNull()?.let { call -> + ResolvedThreadCall( + calleeGroup = call.partiallyAppliedSymbol.signature.symbol.extractMethodGroup(), + callFullNames = resolveCallFullNames(call) + ) + } + } var wrapCallWith: ThreadGroup? = null - val resolvedCall = expression.getResolvedCall(bindingContext) if (resolvedCall != null) { - val callDescriptor = resolvedCall.candidateDescriptor + checkCallExpression(expression, resolvedCall.calleeGroup) - checkCallExpression(expression, callDescriptor) - - val callFullNames = resolveCallFullNames(resolvedCall) + val callFullNames = resolvedCall.callFullNames wrapCallWith = if (workerThreadSwitchingCalls.any { callFullNames.contains(it) }) { ThreadGroup.WORKER @@ -143,28 +128,14 @@ class ThreadSafety( // region Internal - private fun Iterable.extractMethodGroup(): ThreadGroup { - return firstNotNullOfOrNull { annotationDescriptor -> - val type = annotationDescriptor.type - when (type) { - is SimpleType -> { - val typeName = try { - type.fqNameOrNull()?.shortName()?.asString() - } catch (e: IllegalStateException) { - System.err.println(e.message) - null - } - if (typeName == null) { - println("\nUNABLE to get annotation name for $annotationDescriptor") - } - typeName?.toMethodGroup() - } + private class ResolvedThreadCall( + val calleeGroup: ThreadGroup, + val callFullNames: Set + ) - else -> { - println("\nUnknown type class for $type (${type.javaClass})") - null - } - } + private fun KaCallableSymbol.extractMethodGroup(): ThreadGroup { + return annotations.firstNotNullOfOrNull { annotation -> + annotation.classId?.shortClassName?.asString()?.toMethodGroup() } ?: ThreadGroup.UNKNOWN } @@ -173,16 +144,14 @@ class ThreadSafety( .firstOrNull { it.className == this } } - private fun checkCallExpression(expression: KtCallExpression, callDescriptor: CallableDescriptor) { + private fun checkCallExpression(expression: KtCallExpression, calleeGroup: ThreadGroup) { val parentFunGroup = parentFunGroupStack.first() - val calleeGroup = callDescriptor.annotations.extractMethodGroup() val allowedCall = ThreadGroup.allowedCalls[parentFunGroup]?.contains(calleeGroup) if (allowedCall != true) { report( - CodeSmell( - issue, + Finding( Entity.from(expression), "Calling a ${calleeGroup.asAnnotation()} annotated fun " + "from a ${parentFunGroup.asAnnotation()} annotated fun " + @@ -192,21 +161,23 @@ class ThreadSafety( } } - private fun resolveCallFullNames(resolvedCall: ResolvedCall): Set { - val resolvedName = resolvedCall.candidateDescriptor.fqNameOrNull()?.asString() - if (resolvedName == null) return emptySet() - - val hostType = resolvedCall.dispatchReceiver + private fun KaSession.resolveCallFullNames(call: KaFunctionCall<*>): Set { + val callableId = call.partiallyAppliedSymbol.signature.symbol.callableId ?: return emptySet() + val hostType = call.partiallyAppliedSymbol.dispatchReceiver ?.type + // An inherited member is named after the receiver's class (e.g. `LinearLayout.post` rather than `View.post`) + val resolvedName = hostType?.fqClassName() + ?.let { "$it.${callableId.callableName}" } + ?: callableId.asSingleFqName().asString() + return if (hostType != null) { val aliasedName = hostType - .getAbbreviation() - ?.constructor - ?.declarationDescriptor - ?.fqNameOrNull() + .abbreviation + ?.classId + ?.asSingleFqName() ?.let { - resolvedName.replace(hostType.fqTypeName(includeTypeArguments = false), it.asString()) + resolvedName.replace(fqTypeName(hostType, includeTypeArguments = false), it.asString()) } listOf(resolvedName, aliasedName).filterNotNull().toSet() diff --git a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/ThrowingInternalException.kt b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/ThrowingInternalException.kt index f6328b6359..6db092bb2e 100644 --- a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/ThrowingInternalException.kt +++ b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/ThrowingInternalException.kt @@ -7,34 +7,26 @@ package com.datadog.tools.detekt.rules.sdk import com.datadog.tools.detekt.ext.isContainingEntryPointPublic -import io.gitlab.arturbosch.detekt.api.CodeSmell -import io.gitlab.arturbosch.detekt.api.Debt -import io.gitlab.arturbosch.detekt.api.Entity -import io.gitlab.arturbosch.detekt.api.Issue -import io.gitlab.arturbosch.detekt.api.Rule -import io.gitlab.arturbosch.detekt.api.Severity +import dev.detekt.api.Config +import dev.detekt.api.Entity +import dev.detekt.api.Finding +import dev.detekt.api.Rule import org.jetbrains.kotlin.psi.KtThrowExpression /** * A rule to detekt thrown exceptions. * @active */ -class ThrowingInternalException : Rule() { - - override val issue: Issue = Issue( - javaClass.simpleName, - Severity.Defect, - "This rule reports when an exception is thrown from a private or internal class.", - Debt.TWENTY_MINS - ) +class ThrowingInternalException( + config: Config +) : Rule(config, "This rule reports when an exception is thrown from a private or internal class.") { override fun visitThrowExpression(expression: KtThrowExpression) { if (!expression.isContainingEntryPointPublic()) { report( - CodeSmell( - issue, + Finding( Entity.from(expression), - message = "An exception is thrown from an internal or private part of the code." + "An exception is thrown from an internal or private part of the code." ) ) } diff --git a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/TodoWithoutTask.kt b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/TodoWithoutTask.kt index 122bc76103..6182d0fc6a 100644 --- a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/TodoWithoutTask.kt +++ b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/TodoWithoutTask.kt @@ -6,15 +6,12 @@ package com.datadog.tools.detekt.rules.sdk -import io.gitlab.arturbosch.detekt.api.CodeSmell -import io.gitlab.arturbosch.detekt.api.Config -import io.gitlab.arturbosch.detekt.api.Debt -import io.gitlab.arturbosch.detekt.api.Entity -import io.gitlab.arturbosch.detekt.api.Issue -import io.gitlab.arturbosch.detekt.api.Rule -import io.gitlab.arturbosch.detekt.api.Severity -import io.gitlab.arturbosch.detekt.api.config -import org.jetbrains.kotlin.com.intellij.psi.PsiComment +import com.intellij.psi.PsiComment +import dev.detekt.api.Config +import dev.detekt.api.Entity +import dev.detekt.api.Finding +import dev.detekt.api.Rule +import dev.detekt.api.config import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtNamedFunction import org.jetbrains.kotlin.psi.KtObjectDeclaration @@ -25,18 +22,11 @@ import org.jetbrains.kotlin.psi.KtProperty * @active */ class TodoWithoutTask( - ruleSetConfig: Config -) : Rule(ruleSetConfig) { + config: Config +) : Rule(config, "This rule reports when a TODO comment is missing a task number.") { private val deprecatedPrefixes: List by config(defaultValue = emptyList()) - override val issue: Issue = Issue( - javaClass.simpleName, - Severity.Defect, - "This rule reports when a TODO comment is missing a task number.", - Debt.TEN_MINS - ) - override fun visitObjectDeclaration(declaration: KtObjectDeclaration) { declaration.docComment?.let { reportIfInvalid(it) } super.visitObjectDeclaration(declaration) @@ -93,8 +83,7 @@ class TodoWithoutTask( message: String ) { report( - CodeSmell( - issue, + Finding( Entity.from(comment), message ) diff --git a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/UnsafeCallOnNullableType.kt b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/UnsafeCallOnNullableType.kt index 8f854a4c21..935407c819 100644 --- a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/UnsafeCallOnNullableType.kt +++ b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/UnsafeCallOnNullableType.kt @@ -6,12 +6,10 @@ package com.datadog.tools.detekt.rules.sdk -import io.gitlab.arturbosch.detekt.api.CodeSmell -import io.gitlab.arturbosch.detekt.api.Debt -import io.gitlab.arturbosch.detekt.api.Entity -import io.gitlab.arturbosch.detekt.api.Issue -import io.gitlab.arturbosch.detekt.api.Rule -import io.gitlab.arturbosch.detekt.api.Severity +import dev.detekt.api.Config +import dev.detekt.api.Entity +import dev.detekt.api.Finding +import dev.detekt.api.Rule import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.KtUnaryExpression @@ -21,22 +19,16 @@ import org.jetbrains.kotlin.psi.KtUnaryExpression * * This rule will report any use of `!!`, even on non-nullable types. */ -class UnsafeCallOnNullableType : Rule() { - - override val issue: Issue = Issue( - javaClass.simpleName, - Severity.Defect, - "This rule reports when an Unsafe null cast is used (ie: using !!).", - Debt.TWENTY_MINS - ) +class UnsafeCallOnNullableType( + config: Config +) : Rule(config, "This rule reports when an Unsafe null cast is used (ie: using !!).") { override fun visitUnaryExpression(expression: KtUnaryExpression) { super.visitUnaryExpression(expression) if (expression.operationToken == KtTokens.EXCLEXCL) { report( - CodeSmell( - issue, + Finding( Entity.from(expression), "Calling !! on a nullable type will throw a " + "NullPointerException at runtime in case the value is null. " + diff --git a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/UnsafeThirdPartyFunctionCall.kt b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/UnsafeThirdPartyFunctionCall.kt index b2bb16e807..ee8361ae09 100644 --- a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/UnsafeThirdPartyFunctionCall.kt +++ b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/UnsafeThirdPartyFunctionCall.kt @@ -8,17 +8,13 @@ package com.datadog.tools.detekt.rules.sdk import com.datadog.tools.detekt.ext.fqTypeName import com.datadog.tools.detekt.rules.AbstractCallExpressionRule -import io.gitlab.arturbosch.detekt.api.CodeSmell -import io.gitlab.arturbosch.detekt.api.Config -import io.gitlab.arturbosch.detekt.api.Debt -import io.gitlab.arturbosch.detekt.api.Entity -import io.gitlab.arturbosch.detekt.api.Issue -import io.gitlab.arturbosch.detekt.api.Severity -import io.gitlab.arturbosch.detekt.api.config -import io.gitlab.arturbosch.detekt.api.internal.RequiresTypeResolution +import dev.detekt.api.Config +import dev.detekt.api.Entity +import dev.detekt.api.Finding +import dev.detekt.api.config +import org.jetbrains.kotlin.analysis.api.analyze import org.jetbrains.kotlin.psi.KtCallExpression import org.jetbrains.kotlin.psi.KtTryExpression -import org.jetbrains.kotlin.resolve.BindingContext import java.util.Stack /** @@ -28,10 +24,14 @@ import java.util.Stack * Third party functions are detected based on an internal package prefix: any method which has a * package name with this prefix is considered first party, anything else is third party. */ -@RequiresTypeResolution class UnsafeThirdPartyFunctionCall( config: Config -) : AbstractCallExpressionRule(config, includeTypeArguments = false) { +) : AbstractCallExpressionRule( + config, + "This rule reports when a call to an unsafe third party method is made " + + "(i.e. a function throwing an uncaught exception).", + includeTypeArguments = false +) { private val internalPackagePrefix: String by config(defaultValue = "") private val treatUnknownFunctionAsThrowing: Boolean by config(defaultValue = true) @@ -44,7 +44,7 @@ class UnsafeThirdPartyFunctionCall( } private val knownThrowingCallsMap: Map> by lazy { - knownThrowingCalls.map { + knownThrowingCalls.associate { val splitColon = it.split(':') val key = splitColon.first() if (splitColon.size == 1) { @@ -52,26 +52,18 @@ class UnsafeThirdPartyFunctionCall( } val exceptions = splitColon[1].split(',').toList() key to exceptions - }.toMap() + } } private val caughtExceptions = Stack>() // region Rule - override val issue: Issue = Issue( - javaClass.simpleName, - Severity.Defect, - "This rule reports when a call to an unsafe third party method is made " + - "(i.e. a function throwing an uncaught exception).", - Debt.TWENTY_MINS - ) - override fun visitTryExpression(expression: KtTryExpression) { val caughtTypes = expression.catchClauses .mapNotNull { - val typeReference = it.catchParameter?.typeReference - bindingContext.get(BindingContext.TYPE, typeReference)?.fqTypeName() + val typeReference = it.catchParameter?.typeReference ?: return@mapNotNull null + analyze(typeReference) { fqTypeName(typeReference.type) } } caughtExceptions.push(caughtTypes) super.visitTryExpression(expression) @@ -141,7 +133,7 @@ class UnsafeThirdPartyFunctionCall( expression: KtCallExpression, message: String ) { - report(CodeSmell(issue, Entity.from(expression), message = message)) + report(Finding(Entity.from(expression), message)) } // endregion diff --git a/tools/detekt/src/main/resources/META-INF/services/io.gitlab.arturbosch.detekt.api.RuleSetProvider b/tools/detekt/src/main/resources/META-INF/services/dev.detekt.api.RuleSetProvider similarity index 100% rename from tools/detekt/src/main/resources/META-INF/services/io.gitlab.arturbosch.detekt.api.RuleSetProvider rename to tools/detekt/src/main/resources/META-INF/services/dev.detekt.api.RuleSetProvider diff --git a/tools/detekt/src/main/resources/config/config.yml b/tools/detekt/src/main/resources/config/config.yml new file mode 100644 index 0000000000..3dfcd94ff5 --- /dev/null +++ b/tools/detekt/src/main/resources/config/config.yml @@ -0,0 +1,48 @@ +datadog: + active: true + CheckInternal: + active: false + InvalidStringFormat: + active: false + PackageNameVisibility: + active: false + withBreakingChanges: true + ignoredAnnotations: [] + PreferTimeProvider: + active: false + allowedFiles: + - '.*DdRumContentProvider.*' + - '.*TimeProvider.*' + RequireInternal: + active: false + ThreadSafety: + active: false + workerThreadSwitchingCalls: [] + mainThreadSwitchingCalls: [] + ThrowingInternalException: + active: false + TodoWithoutTask: + active: false + deprecatedPrefixes: [] + UnsafeCallOnNullableType: + active: false + UnsafeThirdPartyFunctionCall: + active: false + internalPackagePrefix: '' + treatUnknownFunctionAsThrowing: true + knownThrowingCalls: [] + knownSafeAndroidCalls: [] + knownSafeThirdPartyCalls: [] + +datadog-test-pyramid: + active: false + ApiSurface: + active: false + outputFileName: 'apiSurface.log' + internalPackagePrefix: '' + ignoredAnnotations: [] + ignoredClasses: [] + ApiUsage: + active: false + outputFileName: 'apiUsage.log' + internalPackagePrefix: '' diff --git a/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/pyramid/ApiSurfaceTest.kt b/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/pyramid/ApiSurfaceTest.kt index 905555f5e9..18dc8b1a44 100644 --- a/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/pyramid/ApiSurfaceTest.kt +++ b/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/pyramid/ApiSurfaceTest.kt @@ -6,15 +6,11 @@ package com.datadog.tools.detekt.rules.pyramid +import dev.detekt.test.TestConfig +import dev.detekt.test.lint import fr.xgouchet.elmyr.junit5.ForgeExtension -import io.github.detekt.test.utils.KotlinCoreEnvironmentWrapper -import io.github.detekt.test.utils.createEnvironment -import io.gitlab.arturbosch.detekt.test.TestConfig -import io.gitlab.arturbosch.detekt.test.assertThat -import io.gitlab.arturbosch.detekt.test.compileAndLintWithContext import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import java.io.File @@ -22,18 +18,10 @@ import java.io.File @ExtendWith(ForgeExtension::class) internal class ApiSurfaceTest { - lateinit var kotlinEnv: KotlinCoreEnvironmentWrapper - val fileName = "apiSurfaceOutput.log" - @BeforeEach - fun setup() { - kotlinEnv = createEnvironment() - } - @AfterEach fun tearDown() { - kotlinEnv.dispose() File(fileName).delete() } @@ -55,7 +43,7 @@ internal class ApiSurfaceTest { // When val findings = ApiSurface(config) - .compileAndLintWithContext(kotlinEnv.env, code) + .lint(code) // Then assertThat(findings).hasSize(0) @@ -85,7 +73,7 @@ internal class ApiSurfaceTest { // When val findings = ApiSurface(config) - .compileAndLintWithContext(kotlinEnv.env, code) + .lint(code) // Then assertThat(findings).hasSize(0) @@ -115,7 +103,7 @@ internal class ApiSurfaceTest { // When val findings = ApiSurface(config) - .compileAndLintWithContext(kotlinEnv.env, code) + .lint(code) // Then assertThat(findings).hasSize(0) diff --git a/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/CheckInternalTest.kt b/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/CheckInternalTest.kt index 266687aac5..05a3ac665f 100644 --- a/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/CheckInternalTest.kt +++ b/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/CheckInternalTest.kt @@ -6,10 +6,11 @@ package com.datadog.tools.detekt.rules.sdk +import dev.detekt.api.Config +import dev.detekt.test.lint import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.junit5.ForgeExtension -import io.gitlab.arturbosch.detekt.test.assertThat -import io.gitlab.arturbosch.detekt.test.lint +import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -28,7 +29,7 @@ class CheckInternalTest { } """.trimIndent() - val findings = CheckInternal().lint(code) + val findings = CheckInternal(Config.empty).lint(code) assertThat(findings).hasSize(1) } @@ -46,7 +47,7 @@ class CheckInternalTest { } """.trimIndent() - val findings = CheckInternal().lint(code) + val findings = CheckInternal(Config.empty).lint(code) assertThat(findings).hasSize(1) } @@ -62,7 +63,7 @@ class CheckInternalTest { } """.trimIndent() - val findings = CheckInternal().lint(code) + val findings = CheckInternal(Config.empty).lint(code) assertThat(findings).hasSize(1) } @@ -77,7 +78,7 @@ class CheckInternalTest { } """.trimIndent() - val findings = CheckInternal().lint(code) + val findings = CheckInternal(Config.empty).lint(code) assertThat(findings).hasSize(0) } @@ -93,7 +94,7 @@ class CheckInternalTest { } """.trimIndent() - val findings = CheckInternal().lint(code) + val findings = CheckInternal(Config.empty).lint(code) assertThat(findings).hasSize(1) } @@ -111,7 +112,7 @@ class CheckInternalTest { } """.trimIndent() - val findings = CheckInternal().lint(code) + val findings = CheckInternal(Config.empty).lint(code) assertThat(findings).hasSize(1) } @@ -127,7 +128,7 @@ class CheckInternalTest { } """.trimIndent() - val findings = CheckInternal().lint(code) + val findings = CheckInternal(Config.empty).lint(code) assertThat(findings).hasSize(1) } @@ -142,7 +143,7 @@ class CheckInternalTest { } """.trimIndent() - val findings = CheckInternal().lint(code) + val findings = CheckInternal(Config.empty).lint(code) assertThat(findings).hasSize(0) } } diff --git a/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/InvalidStringFormatTest.kt b/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/InvalidStringFormatTest.kt index 405cb63039..0818e8bcf4 100644 --- a/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/InvalidStringFormatTest.kt +++ b/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/InvalidStringFormatTest.kt @@ -6,12 +6,12 @@ package com.datadog.tools.detekt.rules.sdk +import dev.detekt.api.Config +import dev.detekt.test.lintWithContext +import dev.detekt.test.utils.KotlinEnvironmentContainer +import dev.detekt.test.utils.createEnvironment import fr.xgouchet.elmyr.junit5.ForgeExtension -import io.github.detekt.test.utils.KotlinCoreEnvironmentWrapper -import io.github.detekt.test.utils.createEnvironment -import io.gitlab.arturbosch.detekt.test.assertThat -import io.gitlab.arturbosch.detekt.test.compileAndLintWithContext -import org.junit.jupiter.api.AfterEach +import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -19,18 +19,13 @@ import org.junit.jupiter.api.extension.ExtendWith @ExtendWith(ForgeExtension::class) internal class InvalidStringFormatTest { - lateinit var kotlinEnv: KotlinCoreEnvironmentWrapper + lateinit var kotlinEnv: KotlinEnvironmentContainer @BeforeEach fun setup() { kotlinEnv = createEnvironment() } - @AfterEach - fun tearDown() { - kotlinEnv.dispose() - } - // region Test unresolved format string @Test @@ -45,8 +40,8 @@ internal class InvalidStringFormatTest { """.trimIndent() // When - val findings = InvalidStringFormat() - .compileAndLintWithContext(kotlinEnv.env, code) + val findings = InvalidStringFormat(Config.empty) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(1) @@ -64,8 +59,8 @@ internal class InvalidStringFormatTest { """.trimIndent() // When - val findings = InvalidStringFormat() - .compileAndLintWithContext(kotlinEnv.env, code) + val findings = InvalidStringFormat(Config.empty) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(1) @@ -82,8 +77,8 @@ internal class InvalidStringFormatTest { """.trimIndent() // When - val findings = InvalidStringFormat() - .compileAndLintWithContext(kotlinEnv.env, code) + val findings = InvalidStringFormat(Config.empty) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(1) @@ -100,8 +95,8 @@ internal class InvalidStringFormatTest { """.trimIndent() // When - val findings = InvalidStringFormat() - .compileAndLintWithContext(kotlinEnv.env, code) + val findings = InvalidStringFormat(Config.empty) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(1) @@ -118,8 +113,8 @@ internal class InvalidStringFormatTest { """.trimIndent() // When - val findings = InvalidStringFormat() - .compileAndLintWithContext(kotlinEnv.env, code) + val findings = InvalidStringFormat(Config.empty) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(1) @@ -136,8 +131,8 @@ internal class InvalidStringFormatTest { """.trimIndent() // When - val findings = InvalidStringFormat() - .compileAndLintWithContext(kotlinEnv.env, code) + val findings = InvalidStringFormat(Config.empty) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(1) @@ -158,8 +153,8 @@ internal class InvalidStringFormatTest { """.trimIndent() // When - val findings = InvalidStringFormat() - .compileAndLintWithContext(kotlinEnv.env, code) + val findings = InvalidStringFormat(Config.empty) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(1) @@ -177,8 +172,8 @@ internal class InvalidStringFormatTest { """.trimIndent() // When - val findings = InvalidStringFormat() - .compileAndLintWithContext(kotlinEnv.env, code) + val findings = InvalidStringFormat(Config.empty) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(1) @@ -196,8 +191,8 @@ internal class InvalidStringFormatTest { """.trimIndent() // When - val findings = InvalidStringFormat() - .compileAndLintWithContext(kotlinEnv.env, code) + val findings = InvalidStringFormat(Config.empty) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(1) @@ -216,8 +211,8 @@ internal class InvalidStringFormatTest { """.trimIndent() // When - val findings = InvalidStringFormat() - .compileAndLintWithContext(kotlinEnv.env, code) + val findings = InvalidStringFormat(Config.empty) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(1) @@ -236,8 +231,8 @@ internal class InvalidStringFormatTest { """.trimIndent() // When - val findings = InvalidStringFormat() - .compileAndLintWithContext(kotlinEnv.env, code) + val findings = InvalidStringFormat(Config.empty) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(1) @@ -258,8 +253,8 @@ internal class InvalidStringFormatTest { """.trimIndent() // When - val findings = InvalidStringFormat() - .compileAndLintWithContext(kotlinEnv.env, code) + val findings = InvalidStringFormat(Config.empty) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(1) @@ -276,8 +271,8 @@ internal class InvalidStringFormatTest { """.trimIndent() // When - val findings = InvalidStringFormat() - .compileAndLintWithContext(kotlinEnv.env, code) + val findings = InvalidStringFormat(Config.empty) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(1) @@ -295,8 +290,8 @@ internal class InvalidStringFormatTest { """.trimIndent() // When - val findings = InvalidStringFormat() - .compileAndLintWithContext(kotlinEnv.env, code) + val findings = InvalidStringFormat(Config.empty) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(1) @@ -314,8 +309,8 @@ internal class InvalidStringFormatTest { """.trimIndent() // When - val findings = InvalidStringFormat() - .compileAndLintWithContext(kotlinEnv.env, code) + val findings = InvalidStringFormat(Config.empty) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(1) @@ -334,8 +329,8 @@ internal class InvalidStringFormatTest { """.trimIndent() // When - val findings = InvalidStringFormat() - .compileAndLintWithContext(kotlinEnv.env, code) + val findings = InvalidStringFormat(Config.empty) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(1) @@ -354,8 +349,8 @@ internal class InvalidStringFormatTest { """.trimIndent() // When - val findings = InvalidStringFormat() - .compileAndLintWithContext(kotlinEnv.env, code) + val findings = InvalidStringFormat(Config.empty) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(1) @@ -376,8 +371,8 @@ internal class InvalidStringFormatTest { """.trimIndent() // When - val findings = InvalidStringFormat() - .compileAndLintWithContext(kotlinEnv.env, code) + val findings = InvalidStringFormat(Config.empty) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(1) @@ -393,13 +388,13 @@ internal class InvalidStringFormatTest { val code = """ fun test(): String { - return String.format("Called %1${"$"}d / %2${"$"}f", 3.14f, 42) + return String.format("Called %1\${"$"}d / %2\${"$"}f", 3.14f, 42) } """.trimIndent() // When - val findings = InvalidStringFormat() - .compileAndLintWithContext(kotlinEnv.env, code) + val findings = InvalidStringFormat(Config.empty) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(2) @@ -416,8 +411,8 @@ internal class InvalidStringFormatTest { """.trimIndent() // When - val findings = InvalidStringFormat() - .compileAndLintWithContext(kotlinEnv.env, code) + val findings = InvalidStringFormat(Config.empty) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(3) @@ -434,8 +429,8 @@ internal class InvalidStringFormatTest { """.trimIndent() // When - val findings = InvalidStringFormat() - .compileAndLintWithContext(kotlinEnv.env, code) + val findings = InvalidStringFormat(Config.empty) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(4) @@ -452,8 +447,8 @@ internal class InvalidStringFormatTest { """.trimIndent() // When - val findings = InvalidStringFormat() - .compileAndLintWithContext(kotlinEnv.env, code) + val findings = InvalidStringFormat(Config.empty) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(1) @@ -481,8 +476,8 @@ internal class InvalidStringFormatTest { """.trimIndent() // When - val findings = InvalidStringFormat() - .compileAndLintWithContext(kotlinEnv.env, code) + val findings = InvalidStringFormat(Config.empty) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).isEmpty() @@ -505,8 +500,8 @@ internal class InvalidStringFormatTest { """.trimIndent() // When - val findings = InvalidStringFormat() - .compileAndLintWithContext(kotlinEnv.env, code) + val findings = InvalidStringFormat(Config.empty) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).isEmpty() @@ -527,8 +522,8 @@ internal class InvalidStringFormatTest { """.trimIndent() // When - val findings = InvalidStringFormat() - .compileAndLintWithContext(kotlinEnv.env, code) + val findings = InvalidStringFormat(Config.empty) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).isEmpty() @@ -540,7 +535,8 @@ internal class InvalidStringFormatTest { val code = """ class Foo { - fun format(s: String) { + fun format(s: String): String { + return s } } @@ -551,8 +547,8 @@ internal class InvalidStringFormatTest { """.trimIndent() // When - val findings = InvalidStringFormat() - .compileAndLintWithContext(kotlinEnv.env, code) + val findings = InvalidStringFormat(Config.empty) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).isEmpty() @@ -565,7 +561,8 @@ internal class InvalidStringFormatTest { """ class Foo { companion object { - fun format(s: String) { + fun format(s: String): String { + return s } } } @@ -576,8 +573,8 @@ internal class InvalidStringFormatTest { """.trimIndent() // When - val findings = InvalidStringFormat() - .compileAndLintWithContext(kotlinEnv.env, code) + val findings = InvalidStringFormat(Config.empty) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).isEmpty() @@ -589,13 +586,13 @@ internal class InvalidStringFormatTest { val code = """ fun test(s: String): String { - return s.toLowercase() + return s.plus("!") } """.trimIndent() // When - val findings = InvalidStringFormat() - .compileAndLintWithContext(kotlinEnv.env, code) + val findings = InvalidStringFormat(Config.empty) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).isEmpty() @@ -612,8 +609,8 @@ internal class InvalidStringFormatTest { """.trimIndent() // When - val findings = InvalidStringFormat() - .compileAndLintWithContext(kotlinEnv.env, code) + val findings = InvalidStringFormat(Config.empty) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).isEmpty() @@ -640,8 +637,8 @@ internal class InvalidStringFormatTest { """.trimIndent() // When - val findings = InvalidStringFormat() - .compileAndLintWithContext(kotlinEnv.env, code) + val findings = InvalidStringFormat(Config.empty) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).isEmpty() @@ -664,8 +661,8 @@ internal class InvalidStringFormatTest { """.trimIndent() // When - val findings = InvalidStringFormat() - .compileAndLintWithContext(kotlinEnv.env, code) + val findings = InvalidStringFormat(Config.empty) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(1) @@ -688,8 +685,8 @@ internal class InvalidStringFormatTest { """.trimIndent() // When - val findings = InvalidStringFormat() - .compileAndLintWithContext(kotlinEnv.env, code) + val findings = InvalidStringFormat(Config.empty) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).isEmpty() diff --git a/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/PackageNameVisibilityTest.kt b/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/PackageNameVisibilityTest.kt index d6d3166c38..d7356ce22a 100644 --- a/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/PackageNameVisibilityTest.kt +++ b/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/PackageNameVisibilityTest.kt @@ -7,16 +7,15 @@ package com.datadog.tools.detekt.rules.sdk import com.datadog.tools.detekt.rules.test.FakeAnnotation +import dev.detekt.test.TestConfig +import dev.detekt.test.lint +import dev.detekt.test.utils.KotlinEnvironmentContainer +import dev.detekt.test.utils.createEnvironment import fr.xgouchet.elmyr.annotation.BoolForgery import fr.xgouchet.elmyr.annotation.StringForgery import fr.xgouchet.elmyr.annotation.StringForgeryType import fr.xgouchet.elmyr.junit5.ForgeExtension -import io.github.detekt.test.utils.KotlinCoreEnvironmentWrapper -import io.github.detekt.test.utils.createEnvironment -import io.gitlab.arturbosch.detekt.test.TestConfig -import io.gitlab.arturbosch.detekt.test.assertThat -import io.gitlab.arturbosch.detekt.test.lint -import org.junit.jupiter.api.AfterEach +import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -28,7 +27,7 @@ import java.util.stream.Stream @ExtendWith(ForgeExtension::class) class PackageNameVisibilityTest { - lateinit var kotlinEnv: KotlinCoreEnvironmentWrapper + lateinit var kotlinEnv: KotlinEnvironmentContainer @BoolForgery var fakeWithBreakingChanges: Boolean = false @@ -41,11 +40,6 @@ class PackageNameVisibilityTest { kotlinEnv = createEnvironment() } - @AfterEach - fun tearDown() { - kotlinEnv.dispose() - } - @ParameterizedTest @MethodSource("types") fun `detekt internal top level type in public package`(type: String) { diff --git a/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/PreferTimeProviderTest.kt b/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/PreferTimeProviderTest.kt index 70cf87c50f..7d8547dec2 100644 --- a/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/PreferTimeProviderTest.kt +++ b/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/PreferTimeProviderTest.kt @@ -6,12 +6,12 @@ package com.datadog.tools.detekt.rules.sdk +import dev.detekt.api.Config +import dev.detekt.test.TestConfig +import dev.detekt.test.lint import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.junit5.ForgeExtension -import io.gitlab.arturbosch.detekt.api.Config -import io.gitlab.arturbosch.detekt.test.TestConfig -import io.gitlab.arturbosch.detekt.test.assertThat -import io.gitlab.arturbosch.detekt.test.lint +import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith diff --git a/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/RequireInternalTest.kt b/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/RequireInternalTest.kt index 7e2e63c0e4..f1f5a1e139 100644 --- a/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/RequireInternalTest.kt +++ b/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/RequireInternalTest.kt @@ -6,10 +6,11 @@ package com.datadog.tools.detekt.rules.sdk +import dev.detekt.api.Config +import dev.detekt.test.lint import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.junit5.ForgeExtension -import io.gitlab.arturbosch.detekt.test.assertThat -import io.gitlab.arturbosch.detekt.test.lint +import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -28,7 +29,7 @@ class RequireInternalTest { } """.trimIndent() - val findings = RequireInternal().lint(code) + val findings = RequireInternal(Config.empty).lint(code) assertThat(findings).hasSize(1) } @@ -46,7 +47,7 @@ class RequireInternalTest { } """.trimIndent() - val findings = RequireInternal().lint(code) + val findings = RequireInternal(Config.empty).lint(code) assertThat(findings).hasSize(1) } @@ -62,7 +63,7 @@ class RequireInternalTest { } """.trimIndent() - val findings = RequireInternal().lint(code) + val findings = RequireInternal(Config.empty).lint(code) assertThat(findings).hasSize(1) } @@ -77,7 +78,7 @@ class RequireInternalTest { } """.trimIndent() - val findings = RequireInternal().lint(code) + val findings = RequireInternal(Config.empty).lint(code) assertThat(findings).hasSize(0) } @@ -93,7 +94,7 @@ class RequireInternalTest { } """.trimIndent() - val findings = RequireInternal().lint(code) + val findings = RequireInternal(Config.empty).lint(code) assertThat(findings).hasSize(1) } @@ -111,7 +112,7 @@ class RequireInternalTest { } """.trimIndent() - val findings = RequireInternal().lint(code) + val findings = RequireInternal(Config.empty).lint(code) assertThat(findings).hasSize(1) } @@ -127,7 +128,7 @@ class RequireInternalTest { } """.trimIndent() - val findings = RequireInternal().lint(code) + val findings = RequireInternal(Config.empty).lint(code) assertThat(findings).hasSize(1) } @@ -142,7 +143,7 @@ class RequireInternalTest { } """.trimIndent() - val findings = RequireInternal().lint(code) + val findings = RequireInternal(Config.empty).lint(code) assertThat(findings).hasSize(0) } } diff --git a/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/ThreadSafetyTest.kt b/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/ThreadSafetyTest.kt index 2f8a4c1ace..91b38a1067 100644 --- a/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/ThreadSafetyTest.kt +++ b/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/ThreadSafetyTest.kt @@ -6,46 +6,26 @@ package com.datadog.tools.detekt.rules.sdk -import android.webkit.JavascriptInterface -import androidx.annotation.MainThread -import io.github.detekt.test.utils.KotlinCoreEnvironmentWrapper -import io.github.detekt.test.utils.createEnvironment -import io.gitlab.arturbosch.detekt.api.Config -import io.gitlab.arturbosch.detekt.test.TestConfig -import io.gitlab.arturbosch.detekt.test.assertThat -import io.gitlab.arturbosch.detekt.test.compileAndLintWithContext -import org.junit.jupiter.api.AfterEach +import dev.detekt.api.Config +import dev.detekt.test.TestConfig +import dev.detekt.test.lintWithContext +import dev.detekt.test.utils.KotlinEnvironmentContainer +import dev.detekt.test.utils.createEnvironment +import org.assertj.core.api.Assertions.assertThat +import org.intellij.lang.annotations.Language import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -import java.io.File class ThreadSafetyTest { - lateinit var kotlinEnv: KotlinCoreEnvironmentWrapper + lateinit var kotlinEnv: KotlinEnvironmentContainer lateinit var fakeConfig: Config @BeforeEach fun setup() { fakeConfig = TestConfig() - kotlinEnv = createEnvironment( - // by some reason all Android-related classes are not discovered by DetektKt compiler, - // so need to add them explicitly - additionalRootPaths = listOf( - MainThread::class, - // alternatively we could get it by reading sdk.dir property and pulling android.jar - // for the right API, but since we need only a single annotation symbol, let's just - // import robolectric instead - JavascriptInterface::class - ).map { - File(it.java.protectionDomain.codeSource.location.path) - } - ) - } - - @AfterEach - fun tearDown() { - kotlinEnv.dispose() + kotlinEnv = createEnvironment() } @Test @@ -66,7 +46,7 @@ class ThreadSafetyTest { } """.trimIndent() - val findings = ThreadSafety(fakeConfig).compileAndLintWithContext(kotlinEnv.env, code) + val findings = ThreadSafety(fakeConfig).lintWithContext(kotlinEnv, code, *ANDROID_STUBS) assertThat(findings).hasSize(1) } @@ -89,7 +69,7 @@ class ThreadSafetyTest { } """.trimIndent() - val findings = ThreadSafety(fakeConfig).compileAndLintWithContext(kotlinEnv.env, code) + val findings = ThreadSafety(fakeConfig).lintWithContext(kotlinEnv, code, *ANDROID_STUBS) assertThat(findings).hasSize(1) } @@ -112,7 +92,7 @@ class ThreadSafetyTest { } """.trimIndent() - val findings = ThreadSafety(fakeConfig).compileAndLintWithContext(kotlinEnv.env, code) + val findings = ThreadSafety(fakeConfig).lintWithContext(kotlinEnv, code, *ANDROID_STUBS) assertThat(findings).hasSize(1) } @@ -135,7 +115,7 @@ class ThreadSafetyTest { } """.trimIndent() - val findings = ThreadSafety(fakeConfig).compileAndLintWithContext(kotlinEnv.env, code) + val findings = ThreadSafety(fakeConfig).lintWithContext(kotlinEnv, code, *ANDROID_STUBS) assertThat(findings).hasSize(1) } @@ -158,7 +138,7 @@ class ThreadSafetyTest { } """.trimIndent() - val findings = ThreadSafety(fakeConfig).compileAndLintWithContext(kotlinEnv.env, code) + val findings = ThreadSafety(fakeConfig).lintWithContext(kotlinEnv, code, *ANDROID_STUBS) assertThat(findings).hasSize(1) } @@ -179,7 +159,7 @@ class ThreadSafetyTest { } """.trimIndent() - val findings = ThreadSafety(fakeConfig).compileAndLintWithContext(kotlinEnv.env, code) + val findings = ThreadSafety(fakeConfig).lintWithContext(kotlinEnv, code, *ANDROID_STUBS) assertThat(findings).hasSize(1) } @@ -202,7 +182,7 @@ class ThreadSafetyTest { } """.trimIndent() - val findings = ThreadSafety(fakeConfig).compileAndLintWithContext(kotlinEnv.env, code) + val findings = ThreadSafety(fakeConfig).lintWithContext(kotlinEnv, code, *ANDROID_STUBS) assertThat(findings).hasSize(0) } @@ -224,7 +204,7 @@ class ThreadSafetyTest { } """.trimIndent() - val findings = ThreadSafety(fakeConfig).compileAndLintWithContext(kotlinEnv.env, code) + val findings = ThreadSafety(fakeConfig).lintWithContext(kotlinEnv, code, *ANDROID_STUBS) assertThat(findings).hasSize(0) } @@ -247,7 +227,7 @@ class ThreadSafetyTest { } """.trimIndent() - val findings = ThreadSafety(fakeConfig).compileAndLintWithContext(kotlinEnv.env, code) + val findings = ThreadSafety(fakeConfig).lintWithContext(kotlinEnv, code, *ANDROID_STUBS) assertThat(findings).hasSize(1) } @@ -270,7 +250,7 @@ class ThreadSafetyTest { } """.trimIndent() - val findings = ThreadSafety(fakeConfig).compileAndLintWithContext(kotlinEnv.env, code) + val findings = ThreadSafety(fakeConfig).lintWithContext(kotlinEnv, code, *ANDROID_STUBS) assertThat(findings).hasSize(1) } @@ -293,7 +273,7 @@ class ThreadSafetyTest { } """.trimIndent() - val findings = ThreadSafety(fakeConfig).compileAndLintWithContext(kotlinEnv.env, code) + val findings = ThreadSafety(fakeConfig).lintWithContext(kotlinEnv, code, *ANDROID_STUBS) assertThat(findings).hasSize(1) } @@ -314,7 +294,7 @@ class ThreadSafetyTest { } """.trimIndent() - val findings = ThreadSafety(fakeConfig).compileAndLintWithContext(kotlinEnv.env, code) + val findings = ThreadSafety(fakeConfig).lintWithContext(kotlinEnv, code, *ANDROID_STUBS) assertThat(findings).hasSize(1) } @@ -337,7 +317,7 @@ class ThreadSafetyTest { } """.trimIndent() - val findings = ThreadSafety(fakeConfig).compileAndLintWithContext(kotlinEnv.env, code) + val findings = ThreadSafety(fakeConfig).lintWithContext(kotlinEnv, code, *ANDROID_STUBS) assertThat(findings).hasSize(1) } @@ -360,7 +340,7 @@ class ThreadSafetyTest { } """.trimIndent() - val findings = ThreadSafety(fakeConfig).compileAndLintWithContext(kotlinEnv.env, code) + val findings = ThreadSafety(fakeConfig).lintWithContext(kotlinEnv, code, *ANDROID_STUBS) assertThat(findings).hasSize(1) } @@ -383,7 +363,7 @@ class ThreadSafetyTest { } """.trimIndent() - val findings = ThreadSafety(fakeConfig).compileAndLintWithContext(kotlinEnv.env, code) + val findings = ThreadSafety(fakeConfig).lintWithContext(kotlinEnv, code, *ANDROID_STUBS) assertThat(findings).hasSize(0) } @@ -405,7 +385,7 @@ class ThreadSafetyTest { } """.trimIndent() - val findings = ThreadSafety(fakeConfig).compileAndLintWithContext(kotlinEnv.env, code) + val findings = ThreadSafety(fakeConfig).lintWithContext(kotlinEnv, code, *ANDROID_STUBS) assertThat(findings).hasSize(0) } @@ -428,7 +408,7 @@ class ThreadSafetyTest { } """.trimIndent() - val findings = ThreadSafety(fakeConfig).compileAndLintWithContext(kotlinEnv.env, code) + val findings = ThreadSafety(fakeConfig).lintWithContext(kotlinEnv, code, *ANDROID_STUBS) assertThat(findings).hasSize(1) } @@ -449,7 +429,7 @@ class ThreadSafetyTest { } """.trimIndent() - val findings = ThreadSafety(fakeConfig).compileAndLintWithContext(kotlinEnv.env, code) + val findings = ThreadSafety(fakeConfig).lintWithContext(kotlinEnv, code, *ANDROID_STUBS) assertThat(findings).hasSize(1) } @@ -472,7 +452,7 @@ class ThreadSafetyTest { } """.trimIndent() - val findings = ThreadSafety(fakeConfig).compileAndLintWithContext(kotlinEnv.env, code) + val findings = ThreadSafety(fakeConfig).lintWithContext(kotlinEnv, code, *ANDROID_STUBS) assertThat(findings).hasSize(1) } @@ -495,7 +475,7 @@ class ThreadSafetyTest { } """.trimIndent() - val findings = ThreadSafety(fakeConfig).compileAndLintWithContext(kotlinEnv.env, code) + val findings = ThreadSafety(fakeConfig).lintWithContext(kotlinEnv, code, *ANDROID_STUBS) assertThat(findings).hasSize(1) } @@ -517,7 +497,7 @@ class ThreadSafetyTest { } """.trimIndent() - val findings = ThreadSafety(fakeConfig).compileAndLintWithContext(kotlinEnv.env, code) + val findings = ThreadSafety(fakeConfig).lintWithContext(kotlinEnv, code, *ANDROID_STUBS) assertThat(findings).hasSize(0) } @@ -540,7 +520,7 @@ class ThreadSafetyTest { } """.trimIndent() - val findings = ThreadSafety(fakeConfig).compileAndLintWithContext(kotlinEnv.env, code) + val findings = ThreadSafety(fakeConfig).lintWithContext(kotlinEnv, code, *ANDROID_STUBS) assertThat(findings).hasSize(0) } @@ -563,7 +543,7 @@ class ThreadSafetyTest { } """.trimIndent() - val findings = ThreadSafety(fakeConfig).compileAndLintWithContext(kotlinEnv.env, code) + val findings = ThreadSafety(fakeConfig).lintWithContext(kotlinEnv, code, *ANDROID_STUBS) assertThat(findings).hasSize(1) } @@ -584,7 +564,7 @@ class ThreadSafetyTest { } """.trimIndent() - val findings = ThreadSafety(fakeConfig).compileAndLintWithContext(kotlinEnv.env, code) + val findings = ThreadSafety(fakeConfig).lintWithContext(kotlinEnv, code, *ANDROID_STUBS) assertThat(findings).hasSize(1) } @@ -607,7 +587,7 @@ class ThreadSafetyTest { } """.trimIndent() - val findings = ThreadSafety(fakeConfig).compileAndLintWithContext(kotlinEnv.env, code) + val findings = ThreadSafety(fakeConfig).lintWithContext(kotlinEnv, code, *ANDROID_STUBS) assertThat(findings).hasSize(0) } @@ -630,7 +610,7 @@ class ThreadSafetyTest { } """.trimIndent() - val findings = ThreadSafety(fakeConfig).compileAndLintWithContext(kotlinEnv.env, code) + val findings = ThreadSafety(fakeConfig).lintWithContext(kotlinEnv, code, *ANDROID_STUBS) assertThat(findings).hasSize(0) } @@ -653,7 +633,7 @@ class ThreadSafetyTest { } """.trimIndent() - val findings = ThreadSafety(fakeConfig).compileAndLintWithContext(kotlinEnv.env, code) + val findings = ThreadSafety(fakeConfig).lintWithContext(kotlinEnv, code, *ANDROID_STUBS) assertThat(findings).hasSize(0) } @@ -676,7 +656,7 @@ class ThreadSafetyTest { } """.trimIndent() - val findings = ThreadSafety(fakeConfig).compileAndLintWithContext(kotlinEnv.env, code) + val findings = ThreadSafety(fakeConfig).lintWithContext(kotlinEnv, code, *ANDROID_STUBS) assertThat(findings).hasSize(0) } @@ -698,7 +678,7 @@ class ThreadSafetyTest { } """.trimIndent() - val findings = ThreadSafety(fakeConfig).compileAndLintWithContext(kotlinEnv.env, code) + val findings = ThreadSafety(fakeConfig).lintWithContext(kotlinEnv, code, *ANDROID_STUBS) assertThat(findings).hasSize(0) } @@ -719,7 +699,7 @@ class ThreadSafetyTest { } """.trimIndent() - val findings = ThreadSafety(fakeConfig).compileAndLintWithContext(kotlinEnv.env, code) + val findings = ThreadSafety(fakeConfig).lintWithContext(kotlinEnv, code, *ANDROID_STUBS) assertThat(findings).hasSize(0) } @@ -740,7 +720,7 @@ class ThreadSafetyTest { } """.trimIndent() - val findings = ThreadSafety(fakeConfig).compileAndLintWithContext(kotlinEnv.env, code) + val findings = ThreadSafety(fakeConfig).lintWithContext(kotlinEnv, code, *ANDROID_STUBS) assertThat(findings).hasSize(0) } @@ -761,7 +741,7 @@ class ThreadSafetyTest { } """.trimIndent() - val findings = ThreadSafety(fakeConfig).compileAndLintWithContext(kotlinEnv.env, code) + val findings = ThreadSafety(fakeConfig).lintWithContext(kotlinEnv, code, *ANDROID_STUBS) assertThat(findings).hasSize(0) } @@ -782,7 +762,7 @@ class ThreadSafetyTest { } """.trimIndent() - val findings = ThreadSafety(fakeConfig).compileAndLintWithContext(kotlinEnv.env, code) + val findings = ThreadSafety(fakeConfig).lintWithContext(kotlinEnv, code, *ANDROID_STUBS) assertThat(findings).hasSize(0) } @@ -803,7 +783,7 @@ class ThreadSafetyTest { } """.trimIndent() - val findings = ThreadSafety(fakeConfig).compileAndLintWithContext(kotlinEnv.env, code) + val findings = ThreadSafety(fakeConfig).lintWithContext(kotlinEnv, code, *ANDROID_STUBS) assertThat(findings).hasSize(0) } @@ -824,7 +804,7 @@ class ThreadSafetyTest { } """.trimIndent() - val findings = ThreadSafety(fakeConfig).compileAndLintWithContext(kotlinEnv.env, code) + val findings = ThreadSafety(fakeConfig).lintWithContext(kotlinEnv, code, *ANDROID_STUBS) assertThat(findings).hasSize(0) } @@ -842,7 +822,7 @@ class ThreadSafetyTest { } """.trimIndent() - val findings = ThreadSafety(fakeConfig).compileAndLintWithContext(kotlinEnv.env, code) + val findings = ThreadSafety(fakeConfig).lintWithContext(kotlinEnv, code, *ANDROID_STUBS) assertThat(findings).hasSize(0) } @@ -869,7 +849,7 @@ class ThreadSafetyTest { } """.trimIndent() - val findings = ThreadSafety(fakeConfig).compileAndLintWithContext(kotlinEnv.env, code) + val findings = ThreadSafety(fakeConfig).lintWithContext(kotlinEnv, code, *ANDROID_STUBS) assertThat(findings).hasSize(0) } @@ -901,7 +881,7 @@ class ThreadSafetyTest { } """.trimIndent() - val findings = ThreadSafety(fakeConfig).compileAndLintWithContext(kotlinEnv.env, code) + val findings = ThreadSafety(fakeConfig).lintWithContext(kotlinEnv, code, *ANDROID_STUBS) assertThat(findings).hasSize(0) } @@ -933,7 +913,7 @@ class ThreadSafetyTest { } """.trimIndent() - val findings = ThreadSafety(fakeConfig).compileAndLintWithContext(kotlinEnv.env, code) + val findings = ThreadSafety(fakeConfig).lintWithContext(kotlinEnv, code, *ANDROID_STUBS) assertThat(findings).hasSize(0) } @@ -960,7 +940,39 @@ class ThreadSafetyTest { } """.trimIndent() - val findings = ThreadSafety(fakeConfig).compileAndLintWithContext(kotlinEnv.env, code) + val findings = ThreadSafety(fakeConfig).lintWithContext(kotlinEnv, code, *ANDROID_STUBS) assertThat(findings).hasSize(0) } + + companion object { + // The Analysis API test engine only provides the JDK and the Kotlin stdlib, stub the Android symbols we need + @Language("kotlin") + private val ANDROID_STUBS = arrayOf( + """ + package androidx.annotation + + annotation class AnyThread + annotation class MainThread + annotation class UiThread + annotation class WorkerThread + """.trimIndent(), + """ + package android.webkit + + annotation class JavascriptInterface + """.trimIndent(), + """ + package android.view + + open class View { + fun post(action: Runnable): Boolean = true + } + """.trimIndent(), + """ + package android.widget + + open class LinearLayout : android.view.View() + """.trimIndent() + ) + } } diff --git a/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/ThrowingInternalExceptionTest.kt b/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/ThrowingInternalExceptionTest.kt index 386d20cb17..35a792ee06 100644 --- a/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/ThrowingInternalExceptionTest.kt +++ b/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/ThrowingInternalExceptionTest.kt @@ -6,10 +6,11 @@ package com.datadog.tools.detekt.rules.sdk +import dev.detekt.api.Config +import dev.detekt.test.lint import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.junit5.ForgeExtension -import io.gitlab.arturbosch.detekt.test.assertThat -import io.gitlab.arturbosch.detekt.test.lint +import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -28,7 +29,7 @@ class ThrowingInternalExceptionTest { } """.trimIndent() - val findings = ThrowingInternalException().lint(code) + val findings = ThrowingInternalException(Config.empty).lint(code) assertThat(findings).hasSize(1) } @@ -46,7 +47,7 @@ class ThrowingInternalExceptionTest { } """.trimIndent() - val findings = ThrowingInternalException().lint(code) + val findings = ThrowingInternalException(Config.empty).lint(code) assertThat(findings).hasSize(1) } @@ -62,7 +63,7 @@ class ThrowingInternalExceptionTest { } """.trimIndent() - val findings = ThrowingInternalException().lint(code) + val findings = ThrowingInternalException(Config.empty).lint(code) assertThat(findings).hasSize(1) } @@ -77,7 +78,7 @@ class ThrowingInternalExceptionTest { } """.trimIndent() - val findings = ThrowingInternalException().lint(code) + val findings = ThrowingInternalException(Config.empty).lint(code) assertThat(findings).hasSize(0) } } diff --git a/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/TodoWithoutTaskTest.kt b/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/TodoWithoutTaskTest.kt index c8c401e357..444414f042 100644 --- a/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/TodoWithoutTaskTest.kt +++ b/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/TodoWithoutTaskTest.kt @@ -6,13 +6,13 @@ package com.datadog.tools.detekt.rules.sdk +import dev.detekt.api.Config +import dev.detekt.test.TestConfig +import dev.detekt.test.lint import fr.xgouchet.elmyr.annotation.IntForgery import fr.xgouchet.elmyr.annotation.StringForgery import fr.xgouchet.elmyr.junit5.ForgeExtension -import io.gitlab.arturbosch.detekt.api.Config -import io.gitlab.arturbosch.detekt.test.TestConfig -import io.gitlab.arturbosch.detekt.test.assertThat -import io.gitlab.arturbosch.detekt.test.lint +import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith diff --git a/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/UnsafeCallOnNullableTypeTest.kt b/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/UnsafeCallOnNullableTypeTest.kt index 67c2fca6fa..6d29ee7c28 100644 --- a/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/UnsafeCallOnNullableTypeTest.kt +++ b/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/UnsafeCallOnNullableTypeTest.kt @@ -6,9 +6,10 @@ package com.datadog.tools.detekt.rules.sdk +import dev.detekt.api.Config +import dev.detekt.test.lint import fr.xgouchet.elmyr.junit5.ForgeExtension -import io.gitlab.arturbosch.detekt.test.assertThat -import io.gitlab.arturbosch.detekt.test.lint +import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -24,7 +25,7 @@ internal class UnsafeCallOnNullableTypeTest { } """.trimIndent() - val findings = UnsafeCallOnNullableType().lint(code) + val findings = UnsafeCallOnNullableType(Config.empty).lint(code) assertThat(findings).hasSize(1) } @@ -38,7 +39,7 @@ internal class UnsafeCallOnNullableTypeTest { } """.trimIndent() - val findings = UnsafeCallOnNullableType().lint(code) + val findings = UnsafeCallOnNullableType(Config.empty).lint(code) assertThat(findings).hasSize(0) } @@ -51,7 +52,7 @@ internal class UnsafeCallOnNullableTypeTest { } """.trimIndent() - val findings = UnsafeCallOnNullableType().lint(code) + val findings = UnsafeCallOnNullableType(Config.empty).lint(code) assertThat(findings).hasSize(0) } @@ -64,7 +65,7 @@ internal class UnsafeCallOnNullableTypeTest { } """.trimIndent() - val findings = UnsafeCallOnNullableType().lint(code) + val findings = UnsafeCallOnNullableType(Config.empty).lint(code) assertThat(findings).hasSize(0) } } diff --git a/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/UnsafeThirdPartyFunctionCallTest.kt b/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/UnsafeThirdPartyFunctionCallTest.kt index b2ea325a08..0b7c467599 100644 --- a/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/UnsafeThirdPartyFunctionCallTest.kt +++ b/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/UnsafeThirdPartyFunctionCallTest.kt @@ -6,14 +6,13 @@ package com.datadog.tools.detekt.rules.sdk +import dev.detekt.api.Config +import dev.detekt.test.TestConfig +import dev.detekt.test.lintWithContext +import dev.detekt.test.utils.KotlinEnvironmentContainer +import dev.detekt.test.utils.createEnvironment import fr.xgouchet.elmyr.junit5.ForgeExtension -import io.github.detekt.test.utils.KotlinCoreEnvironmentWrapper -import io.github.detekt.test.utils.createEnvironment -import io.gitlab.arturbosch.detekt.api.Config -import io.gitlab.arturbosch.detekt.test.TestConfig -import io.gitlab.arturbosch.detekt.test.assertThat -import io.gitlab.arturbosch.detekt.test.compileAndLintWithContext -import org.junit.jupiter.api.AfterEach +import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -21,18 +20,13 @@ import org.junit.jupiter.api.extension.ExtendWith @ExtendWith(ForgeExtension::class) internal class UnsafeThirdPartyFunctionCallTest { - lateinit var kotlinEnv: KotlinCoreEnvironmentWrapper + lateinit var kotlinEnv: KotlinEnvironmentContainer @BeforeEach fun setup() { kotlinEnv = createEnvironment() } - @AfterEach - fun tearDown() { - kotlinEnv.dispose() - } - @Test fun `ignore call on internal type`() { // Given @@ -48,7 +42,7 @@ internal class UnsafeThirdPartyFunctionCallTest { // When val findings = UnsafeThirdPartyFunctionCall(config) - .compileAndLintWithContext(kotlinEnv.env, code) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(0) @@ -72,7 +66,7 @@ internal class UnsafeThirdPartyFunctionCallTest { // When val findings = UnsafeThirdPartyFunctionCall(config) - .compileAndLintWithContext(kotlinEnv.env, code) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(1) @@ -100,7 +94,7 @@ internal class UnsafeThirdPartyFunctionCallTest { // When val findings = UnsafeThirdPartyFunctionCall(config) - .compileAndLintWithContext(kotlinEnv.env, code) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(0) @@ -120,7 +114,7 @@ internal class UnsafeThirdPartyFunctionCallTest { // When val findings = UnsafeThirdPartyFunctionCall(TestConfig()) - .compileAndLintWithContext(kotlinEnv.env, code) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(1) @@ -143,7 +137,7 @@ internal class UnsafeThirdPartyFunctionCallTest { // When val findings = UnsafeThirdPartyFunctionCall(config) - .compileAndLintWithContext(kotlinEnv.env, code) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(1) @@ -163,7 +157,7 @@ internal class UnsafeThirdPartyFunctionCallTest { // When val findings = UnsafeThirdPartyFunctionCall(TestConfig()) - .compileAndLintWithContext(kotlinEnv.env, code) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(1) @@ -184,7 +178,7 @@ internal class UnsafeThirdPartyFunctionCallTest { // When val findings = UnsafeThirdPartyFunctionCall(TestConfig()) - .compileAndLintWithContext(kotlinEnv.env, code) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(1) @@ -214,7 +208,7 @@ internal class UnsafeThirdPartyFunctionCallTest { // When val findings = UnsafeThirdPartyFunctionCall(config) - .compileAndLintWithContext(kotlinEnv.env, code) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(1) @@ -232,7 +226,7 @@ internal class UnsafeThirdPartyFunctionCallTest { import java.io.File import java.lang.ArrayIndexOutOfBoundsException - fun test(f: File): Any { + fun test(f: File): Any? { try { return f.inputStream() } catch (e: ArrayIndexOutOfBoundsException) { @@ -243,7 +237,7 @@ internal class UnsafeThirdPartyFunctionCallTest { // When val findings = UnsafeThirdPartyFunctionCall(config) - .compileAndLintWithContext(kotlinEnv.env, code) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(1) @@ -257,7 +251,7 @@ internal class UnsafeThirdPartyFunctionCallTest { import java.io.File import java.lang.ArrayIndexOutOfBoundsException - fun test(f: File): Any { + fun test(f: File): Any? { try { return f.inputStream() } catch (e: ArrayIndexOutOfBoundsException) { @@ -268,7 +262,7 @@ internal class UnsafeThirdPartyFunctionCallTest { // When val findings = UnsafeThirdPartyFunctionCall(TestConfig()) - .compileAndLintWithContext(kotlinEnv.env, code) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(1) @@ -300,7 +294,7 @@ internal class UnsafeThirdPartyFunctionCallTest { // When val findings = UnsafeThirdPartyFunctionCall(config) - .compileAndLintWithContext(kotlinEnv.env, code) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(0) @@ -330,7 +324,7 @@ internal class UnsafeThirdPartyFunctionCallTest { // When val findings = UnsafeThirdPartyFunctionCall(config) - .compileAndLintWithContext(kotlinEnv.env, code) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(0) @@ -360,7 +354,7 @@ internal class UnsafeThirdPartyFunctionCallTest { // When val findings = UnsafeThirdPartyFunctionCall(config) - .compileAndLintWithContext(kotlinEnv.env, code) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(0) @@ -379,7 +373,7 @@ internal class UnsafeThirdPartyFunctionCallTest { import java.io.FileNotFoundException import java.lang.ArrayIndexOutOfBoundsException - actual typealias FNE = java.io.FileNotFoundException + typealias FNE = java.io.FileNotFoundException fun test(f: File): Any? { try { @@ -392,7 +386,7 @@ internal class UnsafeThirdPartyFunctionCallTest { // When val findings = UnsafeThirdPartyFunctionCall(config) - .compileAndLintWithContext(kotlinEnv.env, code) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(0) @@ -431,7 +425,7 @@ internal class UnsafeThirdPartyFunctionCallTest { // When val findings = UnsafeThirdPartyFunctionCall(config) - .compileAndLintWithContext(kotlinEnv.env, code) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(0) @@ -455,7 +449,53 @@ internal class UnsafeThirdPartyFunctionCallTest { // When val findings = UnsafeThirdPartyFunctionCall(config) - .compileAndLintWithContext(kotlinEnv.env, code) + .lintWithContext(kotlinEnv, code) + + // Then + assertThat(findings).hasSize(0) + } + + @Test + fun `ignores call on known safe third party function {enum entry receiver}`() { + // Given + val config = TestConfig( + "knownSafeThirdPartyCalls" to listOf("java.util.concurrent.TimeUnit.toNanos(kotlin.Long)") + ) + val code = + """ + import java.util.concurrent.TimeUnit + + fun test(durationMs: Long): Long { + return TimeUnit.MILLISECONDS.toNanos(durationMs) + } + """.trimIndent() + + // When + val findings = UnsafeThirdPartyFunctionCall(config) + .lintWithContext(kotlinEnv, code) + + // Then + assertThat(findings).hasSize(0) + } + + @Test + fun `ignores call on known safe third party function {statically imported Java function}`() { + // Given + val config = TestConfig( + "knownSafeThirdPartyCalls" to listOf("java.lang.reflect.Modifier.isStatic(kotlin.Int)") + ) + val code = + """ + import java.lang.reflect.Modifier.isStatic + + fun test(modifiers: Int): Boolean { + return isStatic(modifiers) + } + """.trimIndent() + + // When + val findings = UnsafeThirdPartyFunctionCall(config) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(0) @@ -481,7 +521,7 @@ internal class UnsafeThirdPartyFunctionCallTest { // When val findings = UnsafeThirdPartyFunctionCall(config) - .compileAndLintWithContext(kotlinEnv.env, code) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(0) @@ -502,7 +542,7 @@ internal class UnsafeThirdPartyFunctionCallTest { // When val findings = UnsafeThirdPartyFunctionCall(config) - .compileAndLintWithContext(kotlinEnv.env, code) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(1) @@ -523,7 +563,7 @@ internal class UnsafeThirdPartyFunctionCallTest { // When val findings = UnsafeThirdPartyFunctionCall(config) - .compileAndLintWithContext(kotlinEnv.env, code) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(0) @@ -544,7 +584,7 @@ internal class UnsafeThirdPartyFunctionCallTest { // When val findings = UnsafeThirdPartyFunctionCall(config) - .compileAndLintWithContext(kotlinEnv.env, code) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(1) @@ -565,7 +605,7 @@ internal class UnsafeThirdPartyFunctionCallTest { // When val findings = UnsafeThirdPartyFunctionCall(config) - .compileAndLintWithContext(kotlinEnv.env, code) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(0) @@ -578,7 +618,7 @@ internal class UnsafeThirdPartyFunctionCallTest { """ import java.io.File - fun test(f: File?): Any { + fun test(f: File?): Any? { return f?.let{ it.apply { println(this.toString()) @@ -589,7 +629,7 @@ internal class UnsafeThirdPartyFunctionCallTest { // When val findings = UnsafeThirdPartyFunctionCall(Config.empty) - .compileAndLintWithContext(kotlinEnv.env, code) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(0) @@ -598,7 +638,7 @@ internal class UnsafeThirdPartyFunctionCallTest { @Test fun `ignore kotlin helper calls { with + run + also + println }`() { // Given - val config = TestConfig("knownSafeThirdPartyCalls" to "java.io.File.readBytes()") + val config = TestConfig("knownSafeThirdPartyCalls" to listOf("java.io.File.readBytes()")) val code = """ import java.io.File @@ -616,7 +656,7 @@ internal class UnsafeThirdPartyFunctionCallTest { // When val findings = UnsafeThirdPartyFunctionCall(config) - .compileAndLintWithContext(kotlinEnv.env, code) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(0) @@ -636,7 +676,7 @@ internal class UnsafeThirdPartyFunctionCallTest { // When val findings = UnsafeThirdPartyFunctionCall(Config.empty) - .compileAndLintWithContext(kotlinEnv.env, code) + .lintWithContext(kotlinEnv, code) // Then assertThat(findings).hasSize(0) From acfaca41ace4fc2e3c23d87267e67f16c6b9c5b5 Mon Sep 17 00:00:00 2001 From: Nikita Ogorodnikov Date: Mon, 12 Oct 2026 14:56:15 +0200 Subject: [PATCH 3/4] RUM-18937: Update unmock plugin version --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9ece4b3e12..02405a75ef 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -68,7 +68,7 @@ detekt = "2.0.0-alpha.1" dokka = "2.2.0" ktlint = "1.5.0" ktlintGradlePlugin = "14.2.0" -unmock = "0.9.0" +unmock = "0.11.0" robolectric = "4.4_r1-robolectric-r2" androidLint = "31.0.2" From 6b817f510a75c8ad6e1b6aa5468aa4e888802374 Mon Sep 17 00:00:00 2001 From: Nikita Ogorodnikov Date: Mon, 12 Oct 2026 17:46:03 +0200 Subject: [PATCH 4/4] RUM-18937: Address review comments --- .../android/log/internal/domain/event/LogEventSerializerTest.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/features/dd-sdk-android-logs/src/test/kotlin/com/datadog/android/log/internal/domain/event/LogEventSerializerTest.kt b/features/dd-sdk-android-logs/src/test/kotlin/com/datadog/android/log/internal/domain/event/LogEventSerializerTest.kt index ad52984b00..2e04c1cf3b 100644 --- a/features/dd-sdk-android-logs/src/test/kotlin/com/datadog/android/log/internal/domain/event/LogEventSerializerTest.kt +++ b/features/dd-sdk-android-logs/src/test/kotlin/com/datadog/android/log/internal/domain/event/LogEventSerializerTest.kt @@ -172,7 +172,6 @@ internal class LogEventSerializerTest { additionalProperties = fakeLog.usr.additionalProperties .toMutableMap() .apply { put(faultyKey, faultyObject) } - .toMutableMap() ) ) @@ -200,7 +199,6 @@ internal class LogEventSerializerTest { additionalProperties = fakeLog.account.additionalProperties .toMutableMap() .apply { put(faultyKey, faultyObject) } - .toMutableMap() ) )