Repository: detekt/detekt (kotlin)

Issue:
Add RequiresAnalysisApi marker interface
<!-- Your feature request may already be reported! -->
<!-- Please search in the [issues](https://github.com/detekt/detekt/issues) before creating a new one. -->

## Expected Behavior
Adding a marker interface for rules that use the Analysis API will assist with migration and is also helpful for test infrastructure.

## Current Behavior
No such interface exists.

## Context
Supports #8021

Pull request:
Closes #8155 

Also adds support for testing rules that have migrated from `RequiresFullAnalysis` to `RequiresAnalysisApi`

Stacks on:
* #8037

Patch:
diff --git a/detekt-api/api/detekt-api.api b/detekt-api/api/detekt-api.api
index 9546946909b5..240fe304cc49 100644
--- a/detekt-api/api/detekt-api.api
+++ b/detekt-api/api/detekt-api.api
@@ -224,6 +224,9 @@ public abstract interface class io/gitlab/arturbosch/detekt/api/ReportingExtensi
 	public fun transformIssues (Ljava/util/List;)Ljava/util/List;
 }
 
+public abstract interface class io/gitlab/arturbosch/detekt/api/RequiresAnalysisApi {
+}
+
 public abstract interface class io/gitlab/arturbosch/detekt/api/RequiresFullAnalysis {
 }
 
diff --git a/detekt-api/src/main/kotlin/io/gitlab/arturbosch/detekt/api/RequiresAnalysisApi.kt b/detekt-api/src/main/kotlin/io/gitlab/arturbosch/detekt/api/RequiresAnalysisApi.kt
new file mode 100644
index 000000000000..91b9acc30a88
--- /dev/null
+++ b/detekt-api/src/main/kotlin/io/gitlab/arturbosch/detekt/api/RequiresAnalysisApi.kt
@@ -0,0 +1,8 @@
+package io.gitlab.arturbosch.detekt.api
+
+/**
+ * Interface to let the core know that this [Rule] uses the Analysis API
+ *
+ * The core will only run your [Rule] if it is running in FullAnalysis mode.
+ */
+interface RequiresAnalysisApi
diff --git a/detekt-core/src/main/kotlin/io/gitlab/arturbosch/detekt/core/RuleDescriptor.kt b/detekt-core/src/main/kotlin/io/gitlab/arturbosch/detekt/core/RuleDescriptor.kt
index 93bd724bafd2..27f4193d450f 100644
--- a/detekt-core/src/main/kotlin/io/gitlab/arturbosch/detekt/core/RuleDescriptor.kt
+++ b/detekt-core/src/main/kotlin/io/gitlab/arturbosch/detekt/core/RuleDescriptor.kt
@@ -1,6 +1,7 @@
 package io.gitlab.arturbosch.detekt.core
 
 import io.gitlab.arturbosch.detekt.api.Config
+import io.gitlab.arturbosch.detekt.api.RequiresAnalysisApi
 import io.gitlab.arturbosch.detekt.api.RequiresFullAnalysis
 import io.gitlab.arturbosch.detekt.api.Rule
 import io.gitlab.arturbosch.detekt.api.RuleInstance
@@ -50,7 +51,7 @@ private fun RuleSet.getRules(
             val rule = ruleProvider(Config.empty)
             val ruleConfig = config.subConfig(ruleId)
             val active = config.isActiveOrDefault(true) && ruleConfig.isActiveOrDefault(false)
-            val executable = fullAnalysis || rule !is RequiresFullAnalysis
+            val executable = fullAnalysis || (rule !is RequiresFullAnalysis && rule !is RequiresAnalysisApi)
             if (active && !executable) {
                 log { "The rule '$ruleId' requires type resolution but it was run without it." }
             }
diff --git a/detekt-generator/src/main/kotlin/io/gitlab/arturbosch/detekt/generator/collection/RuleVisitor.kt b/detekt-generator/src/main/kotlin/io/gitlab/arturbosch/detekt/generator/collection/RuleVisitor.kt
index f8089b916ec3..83037eed86c4 100644
--- a/detekt-generator/src/main/kotlin/io/gitlab/arturbosch/detekt/generator/collection/RuleVisitor.kt
+++ b/detekt-generator/src/main/kotlin/io/gitlab/arturbosch/detekt/generator/collection/RuleVisitor.kt
@@ -3,6 +3,7 @@ package io.gitlab.arturbosch.detekt.generator.collection
 import io.gitlab.arturbosch.detekt.api.ActiveByDefault
 import io.gitlab.arturbosch.detekt.api.Alias
 import io.gitlab.arturbosch.detekt.api.DetektVisitor
+import io.gitlab.arturbosch.detekt.api.RequiresAnalysisApi
 import io.gitlab.arturbosch.detekt.api.RequiresFullAnalysis
 import io.gitlab.arturbosch.detekt.api.internal.AutoCorrectable
 import io.gitlab.arturbosch.detekt.generator.collection.exception.InvalidDocumentationException
@@ -27,6 +28,8 @@ internal class RuleVisitor(textReplacements: Map<String, String>) : DetektVisito
     private val configurationCollector = ConfigurationCollector()
     private val classesMap = mutableMapOf<String, Boolean>()
     private var deprecationMessage: String? = null
+    private val fullAnalysisInterfaces =
+        setOf(RequiresFullAnalysis::class.simpleName, RequiresAnalysisApi::class.simpleName)
 
     fun getRule(): Rule {
         if (documentationCollector.description.isEmpty()) {
@@ -95,7 +98,7 @@ internal class RuleVisitor(textReplacements: Map<String, String>) : DetektVisito
 
         autoCorrect = classOrObject.isAnnotatedWith(AutoCorrectable::class)
         requiresFullAnalysis = classOrObject.superTypeListEntries
-            .any { it.text.substringAfterLast(".") == RequiresFullAnalysis::class.simpleName }
+            .any { it.text.substringAfterLast(".") in fullAnalysisInterfaces }
         deprecationMessage = classOrObject.firstAnnotationParameterOrNull(Deprecated::class)
 
         documentationCollector.setClass(classOrObject)


Test patch:
diff --git a/detekt-test-utils/api/detekt-test-utils.api b/detekt-test-utils/api/detekt-test-utils.api
index 827f13a860d2..484cde6c7be2 100644
--- a/detekt-test-utils/api/detekt-test-utils.api
+++ b/detekt-test-utils/api/detekt-test-utils.api
@@ -12,7 +12,7 @@ public final class io/github/detekt/test/utils/FileExtensionKt {
 
 public final class io/github/detekt/test/utils/KotlinAnalysisApiEngine {
 	public static final field INSTANCE Lio/github/detekt/test/utils/KotlinAnalysisApiEngine;
-	public final fun compile (Ljava/lang/String;)V
+	public final fun compile (Ljava/lang/String;)Lorg/jetbrains/kotlin/psi/KtFile;
 }
 
 public final class io/github/detekt/test/utils/KotlinCoreEnvironmentWrapper {
diff --git a/detekt-test-utils/src/main/kotlin/io/github/detekt/test/utils/KotlinAnalysisApiEngine.kt b/detekt-test-utils/src/main/kotlin/io/github/detekt/test/utils/KotlinAnalysisApiEngine.kt
index 9f09f4e79cc1..e83dfccafb8a 100644
--- a/detekt-test-utils/src/main/kotlin/io/github/detekt/test/utils/KotlinAnalysisApiEngine.kt
+++ b/detekt-test-utils/src/main/kotlin/io/github/detekt/test/utils/KotlinAnalysisApiEngine.kt
@@ -42,7 +42,7 @@ object KotlinAnalysisApiEngine {
      * @throws IllegalStateException if the given code snippet does not compile
      */
     @Suppress("LongMethod")
-    fun compile(@Language("kotlin") code: String) {
+    fun compile(@Language("kotlin") code: String): KtFile {
         val disposable = Disposer.newDisposable()
 
         @OptIn(KaImplementationDetail::class, KaPlatformInterface::class)
@@ -113,6 +113,8 @@ object KotlinAnalysisApiEngine {
                     error(errors)
                 }
             }
+
+            return file
         } finally {
             disposable.dispose()
         }
diff --git a/detekt-test/api/detekt-test.api b/detekt-test/api/detekt-test.api
index d2b834cc9a7b..f8b5d3a6b5b7 100644
--- a/detekt-test/api/detekt-test.api
+++ b/detekt-test/api/detekt-test.api
@@ -46,6 +46,7 @@ public final class io/gitlab/arturbosch/detekt/test/RuleExtensionsKt {
 	public static final fun lint (Lio/gitlab/arturbosch/detekt/api/Rule;Lorg/jetbrains/kotlin/psi/KtFile;Lio/gitlab/arturbosch/detekt/api/CompilerResources;)Ljava/util/List;
 	public static synthetic fun lint$default (Lio/gitlab/arturbosch/detekt/api/Rule;Ljava/lang/String;Lio/gitlab/arturbosch/detekt/api/CompilerResources;ZILjava/lang/Object;)Ljava/util/List;
 	public static synthetic fun lint$default (Lio/gitlab/arturbosch/detekt/api/Rule;Lorg/jetbrains/kotlin/psi/KtFile;Lio/gitlab/arturbosch/detekt/api/CompilerResources;ILjava/lang/Object;)Ljava/util/List;
+	public static final fun lintWithContext (Lio/gitlab/arturbosch/detekt/api/Rule;Lio/github/detekt/test/utils/KotlinEnvironmentContainer;Ljava/lang/String;)Ljava/util/List;
 	public static final fun lintWithContext (Lio/gitlab/arturbosch/detekt/api/Rule;Lio/github/detekt/test/utils/KotlinEnvironmentContainer;Ljava/lang/String;[Ljava/lang/String;Lio/gitlab/arturbosch/detekt/api/CompilerResources;Z)Ljava/util/List;
 	public static synthetic fun lintWithContext$default (Lio/gitlab/arturbosch/detekt/api/Rule;Lio/github/detekt/test/utils/KotlinEnvironmentContainer;Ljava/lang/String;[Ljava/lang/String;Lio/gitlab/arturbosch/detekt/api/CompilerResources;ZILjava/lang/Object;)Ljava/util/List;
 }
diff --git a/detekt-test/src/main/kotlin/io/gitlab/arturbosch/detekt/test/RuleExtensions.kt b/detekt-test/src/main/kotlin/io/gitlab/arturbosch/detekt/test/RuleExtensions.kt
index fc8024d49340..ea9abb7d31fa 100644
--- a/detekt-test/src/main/kotlin/io/gitlab/arturbosch/detekt/test/RuleExtensions.kt
+++ b/detekt-test/src/main/kotlin/io/gitlab/arturbosch/detekt/test/RuleExtensions.kt
@@ -7,6 +7,7 @@ import io.github.detekt.test.utils.compileContentForTest
 import io.gitlab.arturbosch.detekt.api.CompilerResources
 import io.gitlab.arturbosch.detekt.api.Config
 import io.gitlab.arturbosch.detekt.api.Finding
+import io.gitlab.arturbosch.detekt.api.RequiresAnalysisApi
 import io.gitlab.arturbosch.detekt.api.RequiresFullAnalysis
 import io.gitlab.arturbosch.detekt.api.Rule
 import io.gitlab.arturbosch.detekt.api.RuleSet
@@ -65,6 +66,20 @@ fun <T> T.lintWithContext(
     return visitFile(ktFile, compilerResources).filterSuppressed(this)
 }
 
+fun <T> T.lintWithContext(
+    environment: KotlinEnvironmentContainer,
+    @Language("kotlin") content: String,
+): List<Finding> where T : Rule, T : RequiresAnalysisApi {
+    val ktFile = KotlinAnalysisApiEngine.compile(content)
+
+    val compilerResources = CompilerResources(
+        environment.configuration.languageVersionSettings,
+        DataFlowValueFactoryImpl(environment.configuration.languageVersionSettings)
+    )
+
+    return visitFile(ktFile, compilerResources).filterSuppressed(this)
+}
+
 fun Rule.lint(ktFile: KtFile, compilerResources: CompilerResources = FakeCompilerResources()): List<Finding> {
     require(this !is RequiresFullAnalysis) {
         "${this.ruleName} requires full analysis so you should use lintWithContext instead of lint"