Skip to content

Commit

Permalink
SONARKT-400 Integrate kotlin-analysis-api
Browse files Browse the repository at this point in the history
  • Loading branch information
Godin authored Nov 29, 2024
1 parent 2d4603e commit f44acf4
Show file tree
Hide file tree
Showing 109 changed files with 674 additions and 11 deletions.
5 changes: 5 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ allprojects {

repositories {
mavenCentral()
maven(url = "https://maven.pkg.jetbrains.space/kotlin/p/kotlin/kotlin-ide-plugin-dependencies") {
content {
includeGroup("org.jetbrains.kotlin")
}
}
}
}

Expand Down
40 changes: 40 additions & 0 deletions gradle/verification-metadata.xml
Original file line number Diff line number Diff line change
Expand Up @@ -805,6 +805,36 @@
<sha256 value="c5fd725bffab51846bf3c77db1383c60aaaebfe1b7fe2f00d23fe1b7df0a439d" origin="Verified"/>
</artifact>
</component>
<component group="org.jetbrains.kotlin" name="analysis-api-fe10-for-ide" version="2.0.21">
<artifact name="analysis-api-fe10-for-ide-2.0.21.jar">
<sha256 value="2ff6e25ce9d8b3bc43423d5552bbe6e3ba1e37f588601e3b651d240a06aec5d2" origin="Verified"/>
</artifact>
</component>
<component group="org.jetbrains.kotlin" name="analysis-api-for-ide" version="2.0.21">
<artifact name="analysis-api-for-ide-2.0.21.jar">
<sha256 value="3a9436ea39d53eb6686557124a26a914747b1b87acf93fe93d88d8df3db9c467" origin="Verified"/>
</artifact>
</component>
<component group="org.jetbrains.kotlin" name="analysis-api-impl-base-for-ide" version="2.0.21">
<artifact name="analysis-api-impl-base-for-ide-2.0.21.jar">
<sha256 value="507088828a8cc64d727954ded1ba752fb9076d770578c41b892fb70c263af2d6" origin="Verified"/>
</artifact>
</component>
<component group="org.jetbrains.kotlin" name="analysis-api-k2-for-ide" version="2.0.21">
<artifact name="analysis-api-k2-for-ide-2.0.21.jar">
<sha256 value="7bdd4d25d5ab7e2e79ff101ca1827ab67d4da3dfab69fd77ce5de37f1c5cdcb0" origin="Verified"/>
</artifact>
</component>
<component group="org.jetbrains.kotlin" name="analysis-api-platform-interface-for-ide" version="2.0.21">
<artifact name="analysis-api-platform-interface-for-ide-2.0.21.jar">
<sha256 value="91f42f11a73ebd5de4c1c654abe1a5127568c30a3ef41ac72017fc3275fad56b" origin="Verified"/>
</artifact>
</component>
<component group="org.jetbrains.kotlin" name="analysis-api-standalone-for-ide" version="2.0.21">
<artifact name="analysis-api-standalone-for-ide-2.0.21.jar">
<sha256 value="6045d797d9b64f238e13d75ea9b7de07ca76f8b7a7376a1ba0bde617c32ae2f7" origin="Verified"/>
</artifact>
</component>
<component group="org.jetbrains.kotlin" name="kotlin-android-extensions" version="1.9.23">
<artifact name="kotlin-android-extensions-1.9.23.jar">
<sha256 value="61fd7648f931ff4e9bdbff49bf0987705763fbba9c7bb00b264811a36d111ac1" origin="Verified"/>
Expand Down Expand Up @@ -1430,6 +1460,16 @@
<sha256 value="a13b73896554b48e4bef4d8a4630ea7e940149ac4cadccac06914639146549ea" origin="Verified"/>
</artifact>
</component>
<component group="org.jetbrains.kotlin" name="low-level-api-fir-for-ide" version="2.0.21">
<artifact name="low-level-api-fir-for-ide-2.0.21.jar">
<sha256 value="7ffc73a07642c6790ca94d46a70b8c77dd7087ed25de8a41a13edb7727641f5a" origin="Verified"/>
</artifact>
</component>
<component group="org.jetbrains.kotlin" name="symbol-light-classes-for-ide" version="2.0.21">
<artifact name="symbol-light-classes-for-ide-2.0.21.jar">
<sha256 value="44db8c66d15b17062aaea60a18f193c4172bfd0057f84bc5f6ed5db8cfed26e6" origin="Verified"/>
</artifact>
</component>
<component group="org.jetbrains.kotlinx" name="atomicfu" version="0.16.2">
<artifact name="atomicfu-metadata-0.16.2-all.jar">
<sha256 value="f77118b07affb96d705dbf09015a331e689fb8bd7367b982c5076568e6f21874" origin="Verified"/>
Expand Down
21 changes: 21 additions & 0 deletions sonar-kotlin-api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,27 @@ plugins {
}

dependencies {
listOf(
// Source of these artifacts is
// https://github.com/JetBrains/kotlin/tree/v2.0.21/prepare/ide-plugin-dependencies
// where ones whose name contains "high-level" are deprecated and should not be used - see
// https://github.com/JetBrains/kotlin/commit/3ad9798a17ad9eb68cdb1e9f8f1a69584151bfd4
"org.jetbrains.kotlin:analysis-api-standalone-for-ide",
"org.jetbrains.kotlin:analysis-api-platform-interface-for-ide",
"org.jetbrains.kotlin:analysis-api-for-ide", // old name "high-level-api-for-ide"
"org.jetbrains.kotlin:analysis-api-impl-base-for-ide", // old name "high-level-api-impl-base"
"org.jetbrains.kotlin:analysis-api-fe10-for-ide", // old name "high-level-api-fe10"
"org.jetbrains.kotlin:analysis-api-k2-for-ide", // old name "high-level-api-k2"
"org.jetbrains.kotlin:low-level-api-fir-for-ide",
"org.jetbrains.kotlin:symbol-light-classes-for-ide"
).forEach {
val kotlinVersion: String by project.ext
api("$it:$kotlinVersion") {
// https://youtrack.jetbrains.com/issue/KT-61639/Standalone-Analysis-API-cannot-find-transitive-dependencies
isTransitive = false
}
}

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

import com.intellij.core.CoreApplicationEnvironment
import com.intellij.mock.MockApplication
import com.intellij.mock.MockProject
import com.intellij.openapi.Disposable
import com.intellij.psi.ClassTypePointerFactory
import com.intellij.psi.impl.smartPointers.PsiClassReferenceTypePointerFactory
import org.jetbrains.kotlin.analysis.api.KaAnalysisNonPublicApi
import org.jetbrains.kotlin.analysis.api.descriptors.CliFe10AnalysisFacade
import org.jetbrains.kotlin.analysis.api.descriptors.Fe10AnalysisFacade
import org.jetbrains.kotlin.analysis.api.descriptors.KaFe10AnalysisHandlerExtension
import org.jetbrains.kotlin.analysis.api.platform.lifetime.KotlinAlwaysAccessibleLifetimeTokenProvider
import org.jetbrains.kotlin.analysis.api.platform.lifetime.KotlinLifetimeTokenProvider
import org.jetbrains.kotlin.analysis.api.platform.modification.KotlinGlobalModificationService
import org.jetbrains.kotlin.analysis.api.platform.modification.KotlinModificationTrackerFactory
import org.jetbrains.kotlin.analysis.api.platform.projectStructure.KotlinByModulesResolutionScopeProvider
import org.jetbrains.kotlin.analysis.api.platform.projectStructure.KotlinProjectStructureProvider
import org.jetbrains.kotlin.analysis.api.platform.projectStructure.KotlinResolutionScopeProvider
import org.jetbrains.kotlin.analysis.api.standalone.base.modification.KotlinStandaloneGlobalModificationService
import org.jetbrains.kotlin.analysis.api.standalone.base.modification.KotlinStandaloneModificationTrackerFactory
import org.jetbrains.kotlin.analysis.api.standalone.base.projectStructure.AnalysisApiSimpleServiceRegistrar
import org.jetbrains.kotlin.analysis.api.standalone.base.projectStructure.PluginStructureProvider
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.references.fe10.base.DummyKtFe10ReferenceResolutionHelper
import org.jetbrains.kotlin.references.fe10.base.KtFe10ReferenceResolutionHelper
import org.jetbrains.kotlin.resolve.extensions.AnalysisHandlerExtension

/**
* Marker indicating that
* annotated [org.sonarsource.kotlin.api.checks.KotlinCheck]
* can be executed only in K1 mode.
*/
annotation class K1only

internal fun configureK1AnalysisApiServices(env: KotlinCoreEnvironment) {
val application = env.projectEnvironment.environment.application
if (application.getServiceIfCreated(KtFe10ReferenceResolutionHelper::class.java) == null) {
AnalysisApiFe10ServiceRegistrar.registerApplicationServices(application)
}
val project = env.projectEnvironment.project
AnalysisApiFe10ServiceRegistrar.registerProjectServices(project)
AnalysisApiFe10ServiceRegistrar.registerProjectModelServices(
project,
env.projectEnvironment.parentDisposable
)

project.registerService(
KotlinModificationTrackerFactory::class.java,
KotlinStandaloneModificationTrackerFactory::class.java,
)
project.registerService(
KotlinGlobalModificationService::class.java,
KotlinStandaloneGlobalModificationService::class.java,
)
project.registerService(
KotlinLifetimeTokenProvider::class.java,
KotlinAlwaysAccessibleLifetimeTokenProvider::class.java,
)
project.registerService(
KotlinResolutionScopeProvider::class.java,
KotlinByModulesResolutionScopeProvider::class.java,
);
project.registerService(
KotlinProjectStructureProvider::class.java,
KtModuleProviderByCompilerConfiguration.build(
env.projectEnvironment,
env.configuration,
listOf()
)
)
}

@OptIn(KaAnalysisNonPublicApi::class)
private object AnalysisApiFe10ServiceRegistrar : AnalysisApiSimpleServiceRegistrar() {
private const val PLUGIN_RELATIVE_PATH = "/META-INF/analysis-api/analysis-api-fe10.xml"

override fun registerApplicationServices(application: MockApplication) {
PluginStructureProvider.registerApplicationServices(application, PLUGIN_RELATIVE_PATH)
application.registerService(
KtFe10ReferenceResolutionHelper::class.java,
DummyKtFe10ReferenceResolutionHelper,
)
val applicationArea = application.extensionArea
if (!applicationArea.hasExtensionPoint(ClassTypePointerFactory.EP_NAME)) {
CoreApplicationEnvironment.registerApplicationExtensionPoint(
ClassTypePointerFactory.EP_NAME,
ClassTypePointerFactory::class.java,
)
applicationArea
.getExtensionPoint(ClassTypePointerFactory.EP_NAME)
.registerExtension(PsiClassReferenceTypePointerFactory(), application)
}
}

override fun registerProjectExtensionPoints(project: MockProject) {
AnalysisHandlerExtension.registerExtensionPoint(project)
PluginStructureProvider.registerProjectExtensionPoints(project, PLUGIN_RELATIVE_PATH)
}

override fun registerProjectServices(project: MockProject) {
PluginStructureProvider.registerProjectServices(project, PLUGIN_RELATIVE_PATH)
PluginStructureProvider.registerProjectListeners(project, PLUGIN_RELATIVE_PATH)
}

override fun registerProjectModelServices(project: MockProject, disposable: Disposable) {
project.apply { registerService(Fe10AnalysisFacade::class.java, CliFe10AnalysisFacade()) }
AnalysisHandlerExtension.registerExtension(project, KaFe10AnalysisHandlerExtension())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/*
* SonarSource Kotlin
* Copyright (C) 2018-2024 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
package org.sonarsource.kotlin.api.frontend

import com.intellij.openapi.Disposable
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileSystem
import com.intellij.openapi.vfs.local.CoreLocalFileSystem
import org.jetbrains.kotlin.analysis.api.standalone.StandaloneAnalysisAPISession
import org.jetbrains.kotlin.analysis.api.standalone.buildStandaloneAnalysisAPISession
import org.jetbrains.kotlin.analysis.project.structure.builder.buildKtLibraryModule
import org.jetbrains.kotlin.analysis.project.structure.builder.buildKtSdkModule
import org.jetbrains.kotlin.analysis.project.structure.builder.buildKtSourceModule
import org.jetbrains.kotlin.cli.common.CliModuleVisibilityManagerImpl
import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.load.kotlin.ModuleVisibilityManager
import org.jetbrains.kotlin.platform.jvm.JvmPlatforms
import java.io.File
import java.io.InputStream
import java.io.OutputStream

/**
* @see [org.jetbrains.kotlin.analysis.api.standalone.StandaloneAnalysisAPISessionBuilder.buildKtModuleProviderByCompilerConfiguration]
*/
fun createK2AnalysisSession(
parentDisposable: Disposable,
compilerConfiguration: CompilerConfiguration,
virtualFiles: Collection<VirtualFile>,
): StandaloneAnalysisAPISession {
return buildStandaloneAnalysisAPISession(
projectDisposable = parentDisposable,
) {
// https://github.com/JetBrains/kotlin/blob/a9ff22693479cabd201909a06e6764c00eddbf7b/analysis/analysis-api-fe10/tests/org/jetbrains/kotlin/analysis/api/fe10/test/configurator/AnalysisApiFe10TestServiceRegistrar.kt#L49
registerProjectService(ModuleVisibilityManager::class.java, CliModuleVisibilityManagerImpl(enabled = true))

// TODO language version, jvm target, etc
val platform = JvmPlatforms.defaultJvmPlatform
buildKtModuleProvider {
this.platform = platform
addModule(buildKtSourceModule {
this.platform = platform
moduleName = "module"
addSourceVirtualFiles(virtualFiles)
addRegularDependency(buildKtLibraryModule {
this.platform = platform
libraryName = "library"
addBinaryRoots(compilerConfiguration.jvmClasspathRoots.map { it.toPath() })
})
compilerConfiguration[JVMConfigurationKeys.JDK_HOME]?.let { jdkHome ->
addRegularDependency(buildKtSdkModule {
this.platform = platform
addBinaryRootsFromJdkHome(jdkHome.toPath(), isJre = false)
libraryName = "JDK"
})
}
})
}
}
}

class KotlinFileSystem : CoreLocalFileSystem() {
/**
* TODO return null if file does not exist - see [CoreLocalFileSystem.findFileByNioFile]
*/
override fun findFileByPath(path: String): VirtualFile? =
KotlinVirtualFile(this, File(path))
}

class KotlinVirtualFile(
private val fileSystem: KotlinFileSystem,
private val file: File,
private val content: String? = null,
) : VirtualFile() {

override fun getName(): String = file.name

override fun getFileSystem(): VirtualFileSystem = fileSystem

override fun getPath(): String = FileUtil.toSystemIndependentName(file.absolutePath)

override fun isWritable(): Boolean = false

override fun isDirectory(): Boolean = file.isDirectory

override fun isValid(): Boolean = true

override fun getParent(): VirtualFile? {
val parentFile = file.parentFile ?: return null
return KotlinVirtualFile(fileSystem, parentFile)
}

override fun getChildren(): Array<VirtualFile> {
if (file.isFile || !file.exists()) return emptyArray()
throw UnsupportedOperationException("getChildren " + file.absolutePath)
}

override fun getOutputStream(p0: Any?, p1: Long, p2: Long): OutputStream =
throw UnsupportedOperationException()

override fun contentsToByteArray(): ByteArray {
if (content != null) return content.toByteArray()
return FileUtil.loadFileBytes(file)
}

override fun getTimeStamp(): Long =
throw UnsupportedOperationException()

override fun getLength(): Long = file.length()

override fun refresh(p0: Boolean, p1: Boolean, p2: Runnable?) =
throw UnsupportedOperationException()

override fun getInputStream(): InputStream =
throw UnsupportedOperationException()

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import org.jetbrains.kotlin.cli.jvm.compiler.NoScopeRecordCliBindingTrace
import org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM
import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots
import com.intellij.openapi.Disposable
import com.intellij.openapi.util.Disposer
import org.jetbrains.kotlin.analysis.api.standalone.StandaloneAnalysisAPISession
import org.jetbrains.kotlin.config.ApiVersion
import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.CompilerConfiguration
Expand All @@ -52,10 +52,20 @@ class Environment(
val classpath: List<String>,
kotlinLanguageVersion: LanguageVersion,
javaLanguageVersion: JvmTarget = JvmTarget.JVM_1_8,
val useK2: Boolean = false,
) {
val configuration = compilerConfiguration(classpath, kotlinLanguageVersion, javaLanguageVersion)
// K1
val env = kotlinCoreEnvironment(configuration, disposable)
val ktPsiFactory: KtPsiFactory = KtPsiFactory(env.project, false)
// K2
var k2session: StandaloneAnalysisAPISession? = null

init {
if (!useK2) {
configureK1AnalysisApiServices(env)
}
}
}

fun kotlinCoreEnvironment(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ import org.sonarsource.kotlin.api.reporting.SecondaryLocation
data class KotlinFileContext(
val inputFileContext: InputFileContext,
val ktFile: KtFile,
/**
* @see [org.sonarsource.kotlin.api.visiting.withKaSession]
*/
@Deprecated("use kotlin-analysis-api instead")
val bindingContext: BindingContext,
val diagnostics: List<Diagnostic>,
val regexCache: RegexCache,
Expand Down
Loading

0 comments on commit f44acf4

Please sign in to comment.