Rewrite scripting related API to PsiFile instead of VirtualFile

There were several places where we converted virtualFile to PsiFile. This operation need a read access and may throw ProcessCanceledException,
so we want to minimize its usages in IDE
This commit is contained in:
Natalia Selezneva
2019-08-08 13:47:39 +03:00
parent 5b48dcca4f
commit dc46f73ecf
30 changed files with 278 additions and 211 deletions
@@ -90,7 +90,7 @@ sealed class ScriptDependenciesInfo(val project: Project) : IdeaModuleInfo, Bina
) : ScriptDependenciesInfo(project) { ) : ScriptDependenciesInfo(project) {
override val sdk: Sdk? override val sdk: Sdk?
get() { get() {
return ScriptDependenciesManager.getInstance(project).getScriptSdk(scriptFile, project) return ScriptDependenciesManager.getInstance(project).getScriptSdk(scriptFile)
} }
override fun contentScope(): GlobalSearchScope { override fun contentScope(): GlobalSearchScope {
@@ -71,7 +71,7 @@ object KotlinHighlightingUtil {
private fun shouldHighlightScript(ktFile: KtFile): Boolean { private fun shouldHighlightScript(ktFile: KtFile): Boolean {
if (isRunningInCidrIde) return false // There is no Java support in CIDR. So do not highlight errors in KTS if running in CIDR. if (isRunningInCidrIde) return false // There is no Java support in CIDR. So do not highlight errors in KTS if running in CIDR.
if (!ScriptsCompilationConfigurationUpdater.areDependenciesCached(ktFile)) return false if (!ScriptsCompilationConfigurationUpdater.areDependenciesCached(ktFile)) return false
if (IdeScriptReportSink.getReports(ktFile.virtualFile).any { it.severity == ScriptDiagnostic.Severity.FATAL }) { if (IdeScriptReportSink.getReports(ktFile).any { it.severity == ScriptDiagnostic.Severity.FATAL }) {
return false return false
} }
@@ -53,7 +53,7 @@ class ScriptExternalHighlightingPass(
if (!file.isScript()) return if (!file.isScript()) return
val reports = IdeScriptReportSink.getReports(file.virtualFile) val reports = IdeScriptReportSink.getReports(file)
val annotations = reports.mapNotNull { scriptDiagnostic -> val annotations = reports.mapNotNull { scriptDiagnostic ->
val (startOffset, endOffset) = scriptDiagnostic.location?.let { computeOffsets(document, it) } ?: 0 to 0 val (startOffset, endOffset) = scriptDiagnostic.location?.let { computeOffsets(document, it) } ?: 0 to 0
@@ -23,6 +23,7 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile import com.intellij.psi.PsiFile
import com.intellij.psi.PsiManager
import com.intellij.psi.impl.cache.impl.id.IdIndex import com.intellij.psi.impl.cache.impl.id.IdIndex
import com.intellij.psi.impl.cache.impl.id.IdIndexEntry import com.intellij.psi.impl.cache.impl.id.IdIndexEntry
import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.GlobalSearchScope
@@ -123,7 +124,7 @@ fun PsiSearchHelper.isCheapEnoughToSearchConsideringOperators(
return isCheapEnoughToSearch(name, scope, fileToIgnoreOccurrencesIn, progress) return isCheapEnoughToSearch(name, scope, fileToIgnoreOccurrencesIn, progress)
} }
fun findScriptsWithUsages(declaration: KtNamedDeclaration): List<VirtualFile> { fun findScriptsWithUsages(declaration: KtNamedDeclaration): List<KtFile> {
val project = declaration.project val project = declaration.project
val scope = PsiSearchHelper.getInstance(project).getUseScope(declaration) as? GlobalSearchScope val scope = PsiSearchHelper.getInstance(project).getUseScope(declaration) as? GlobalSearchScope
?: return emptyList() ?: return emptyList()
@@ -138,5 +139,8 @@ fun findScriptsWithUsages(declaration: KtNamedDeclaration): List<VirtualFile> {
scope scope
) )
} }
return collector.results.filter { it.findScriptDefinition(project) != null }.toList() return collector.results
.mapNotNull { PsiManager.getInstance(project).findFile(it) as? KtFile }
.filter { it.findScriptDefinition() != null }
.toList()
} }
@@ -23,6 +23,7 @@ import com.intellij.openapi.util.Key
import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiManager import com.intellij.psi.PsiManager
import com.intellij.ui.EditorNotifications import com.intellij.ui.EditorNotifications
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.UserDataProperty import org.jetbrains.kotlin.psi.UserDataProperty
import org.jetbrains.kotlin.scripting.resolve.ScriptReportSink import org.jetbrains.kotlin.scripting.resolve.ScriptReportSink
import kotlin.script.experimental.api.ScriptDiagnostic import kotlin.script.experimental.api.ScriptDiagnostic
@@ -49,6 +50,10 @@ class IdeScriptReportSink(val project: Project) : ScriptReportSink {
return file.scriptDiagnostics ?: emptyList() return file.scriptDiagnostics ?: emptyList()
} }
fun getReports(file: KtFile): List<ScriptDiagnostic> {
return file.originalFile.virtualFile?.scriptDiagnostics ?: emptyList()
}
private var VirtualFile.scriptDiagnostics: List<ScriptDiagnostic>? by UserDataProperty(Key.create("KOTLIN_SCRIPT_DIAGNOSTICS")) private var VirtualFile.scriptDiagnostics: List<ScriptDiagnostic>? by UserDataProperty(Key.create("KOTLIN_SCRIPT_DIAGNOSTICS"))
} }
} }
@@ -18,10 +18,18 @@ package org.jetbrains.kotlin.idea.core.script
import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.idea.core.util.* import org.jetbrains.kotlin.idea.core.util.*
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.definitions.getScriptOriginalFile
import java.io.* import java.io.*
import kotlin.script.experimental.api.ScriptCompilationConfiguration import kotlin.script.experimental.api.ScriptCompilationConfiguration
import kotlin.script.experimental.dependencies.ScriptDependencies import kotlin.script.experimental.dependencies.ScriptDependencies
var KtFile.scriptDependencies: ScriptDependencies?
get() = this.getScriptOriginalFile()?.virtualFile?.scriptDependencies
set(value) {
this.getScriptOriginalFile()?.virtualFile?.scriptDependencies = value
}
var VirtualFile.scriptDependencies: ScriptDependencies? by cachedFileAttribute( var VirtualFile.scriptDependencies: ScriptDependencies? by cachedFileAttribute(
name = "kotlin-script-dependencies", name = "kotlin-script-dependencies",
version = 3, version = 3,
@@ -45,6 +53,12 @@ var VirtualFile.scriptDependencies: ScriptDependencies? by cachedFileAttribute(
} }
) )
var KtFile.scriptCompilationConfiguration: ScriptCompilationConfiguration?
get() = this.getScriptOriginalFile()?.virtualFile?.scriptCompilationConfiguration
set(value) {
this.getScriptOriginalFile()?.virtualFile?.scriptCompilationConfiguration = value
}
var VirtualFile.scriptCompilationConfiguration: ScriptCompilationConfiguration? by cachedFileAttribute( var VirtualFile.scriptCompilationConfiguration: ScriptCompilationConfiguration? by cachedFileAttribute(
name = "kotlin-script-compilation-configuration", name = "kotlin-script-compilation-configuration",
version = 1, version = 1,
@@ -23,16 +23,17 @@ import com.intellij.openapi.projectRoots.JavaSdkType
import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.roots.ProjectRootManager import com.intellij.openapi.roots.ProjectRootManager
import com.intellij.openapi.vfs.StandardFileSystems import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiManager
import com.intellij.util.io.URLUtil import com.intellij.util.io.URLUtil
import org.jetbrains.annotations.TestOnly import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.idea.caches.project.getAllProjectSdks import org.jetbrains.kotlin.idea.caches.project.getAllProjectSdks
import org.jetbrains.kotlin.idea.core.script.dependencies.SyncScriptDependenciesLoader import org.jetbrains.kotlin.idea.core.script.dependencies.SyncScriptDependenciesLoader
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.definitions.ScriptDependenciesProvider import org.jetbrains.kotlin.scripting.definitions.ScriptDependenciesProvider
import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
import java.io.File import java.io.File
import kotlin.script.experimental.api.valueOrNull import kotlin.script.experimental.api.valueOrNull
@@ -40,28 +41,35 @@ import kotlin.script.experimental.api.valueOrNull
// NOTE: this service exists exclusively because ScriptDependencyManager // NOTE: this service exists exclusively because ScriptDependencyManager
// cannot be registered as implementing two services (state would be duplicated) // cannot be registered as implementing two services (state would be duplicated)
class IdeScriptDependenciesProvider( class IdeScriptDependenciesProvider(
private val scriptDependenciesManager: ScriptDependenciesManager private val scriptDependenciesManager: ScriptDependenciesManager,
) : ScriptDependenciesProvider { project: Project
override fun getScriptConfigurationResult(file: VirtualFile): ScriptCompilationConfigurationResult? = scriptDependenciesManager.getRefinedCompilationConfiguration(file) ) : ScriptDependenciesProvider(project) {
override fun getScriptConfigurationResult(file: KtFile): ScriptCompilationConfigurationResult? {
return scriptDependenciesManager.getRefinedCompilationConfiguration(file)
}
} }
// TODO: rename and provide alias for compatibility - this is not only about dependencies anymore // TODO: rename and provide alias for compatibility - this is not only about dependencies anymore
class ScriptDependenciesManager internal constructor( class ScriptDependenciesManager internal constructor(
private val cacheUpdater: ScriptsCompilationConfigurationUpdater, private val cacheUpdater: ScriptsCompilationConfigurationUpdater,
private val cache: ScriptsCompilationConfigurationCache private val cache: ScriptsCompilationConfigurationCache,
private val project: Project
) { ) {
fun getScriptClasspath(file: VirtualFile): List<VirtualFile> =
toVfsRoots(cacheUpdater.getCurrentCompilationConfiguration(file)?.valueOrNull()?.dependenciesClassPath.orEmpty())
fun getRefinedCompilationConfiguration(file: VirtualFile): ScriptCompilationConfigurationResult? = @Deprecated("Use getScriptClasspath(KtFile) instead")
cacheUpdater.getCurrentCompilationConfiguration(file) fun getScriptClasspath(file: VirtualFile): List<VirtualFile> {
val ktFile = PsiManager.getInstance(project).findFile(file) as? KtFile ?: return emptyList()
fun getScriptSdk(file: VirtualFile, project: Project): Sdk? { return getScriptClasspath(ktFile)
return getScriptSdk(getRefinedCompilationConfiguration(file)?.valueOrNull())
?: getScriptDefaultSdk(project)
} }
fun getScriptClasspath(file: KtFile): List<VirtualFile> =
toVfsRoots(cacheUpdater.getCurrentCompilationConfiguration(file)?.valueOrNull()?.dependenciesClassPath.orEmpty())
fun getRefinedCompilationConfiguration(file: KtFile): ScriptCompilationConfigurationResult? =
cacheUpdater.getCurrentCompilationConfiguration(file)
fun getScriptDependenciesClassFilesScope(file: VirtualFile) = cache.scriptDependenciesClassFilesScope(file) fun getScriptDependenciesClassFilesScope(file: VirtualFile) = cache.scriptDependenciesClassFilesScope(file)
fun getScriptSdk(file: VirtualFile) = cache.getScriptSdk(file)
fun getAllScriptsSdks() = cache.allSdks fun getAllScriptsSdks() = cache.allSdks
@@ -76,17 +84,6 @@ class ScriptDependenciesManager internal constructor(
fun getInstance(project: Project): ScriptDependenciesManager = fun getInstance(project: Project): ScriptDependenciesManager =
ServiceManager.getService(project, ScriptDependenciesManager::class.java) ServiceManager.getService(project, ScriptDependenciesManager::class.java)
fun getScriptSdk(compilationConfiguration: ScriptCompilationConfigurationWrapper?): Sdk? {
// workaround for mismatched gradle wrapper and plugin version
val javaHome = try {
compilationConfiguration?.javaHome?.let { VfsUtil.findFileByIoFile(it, true) }
} catch (e: Throwable) {
null
} ?: return null
return getAllProjectSdks().find { it.homeDirectory == javaHome }
}
fun getScriptDefaultSdk(project: Project): Sdk? { fun getScriptDefaultSdk(project: Project): Sdk? {
val projectSdk = ProjectRootManager.getInstance(project).projectSdk?.takeIf { it.canBeUsedForScript() } val projectSdk = ProjectRootManager.getInstance(project).projectSdk?.takeIf { it.canBeUsedForScript() }
if (projectSdk != null) return projectSdk if (projectSdk != null) return projectSdk
@@ -124,10 +121,13 @@ class ScriptDependenciesManager internal constructor(
internal val log = Logger.getInstance(ScriptDependenciesManager::class.java) internal val log = Logger.getInstance(ScriptDependenciesManager::class.java)
@TestOnly @TestOnly
fun updateScriptDependenciesSynchronously(virtualFile: VirtualFile, project: Project) { fun updateScriptDependenciesSynchronously(file: PsiFile, project: Project) {
val loader = SyncScriptDependenciesLoader(project) val loader = SyncScriptDependenciesLoader(project)
val scriptDefinition = virtualFile.findScriptDefinition(project) ?: return val scriptDefinition = file.findScriptDefinition() ?: return
loader.loadDependencies(virtualFile, scriptDefinition) assert(file is KtFile) {
"PsiFile should be a KtFile, otherwise script dependencies cannot be loaded"
}
loader.loadDependencies(file as KtFile, scriptDefinition)
loader.notifyRootsChanged() loader.notifyRootsChanged()
} }
} }
@@ -28,9 +28,9 @@ fun VirtualFile.removeScriptDependenciesNotificationPanel(project: Project) {
} }
fun VirtualFile.addScriptDependenciesNotificationPanel( fun VirtualFile.addScriptDependenciesNotificationPanel(
compilationConfigurationResult: ScriptCompilationConfigurationResult?, compilationConfigurationResult: ScriptCompilationConfigurationResult,
project: Project, project: Project,
onClick: (ScriptCompilationConfigurationResult?) -> Unit onClick: (ScriptCompilationConfigurationResult) -> Unit
) { ) {
withSelectedEditor(project) { manager -> withSelectedEditor(project) { manager ->
val existingPanel = notificationPanel val existingPanel = notificationPanel
@@ -61,8 +61,8 @@ private fun VirtualFile.withSelectedEditor(project: Project, f: FileEditor.(File
private var FileEditor.notificationPanel: NewScriptDependenciesNotificationPanel? by UserDataProperty<FileEditor, NewScriptDependenciesNotificationPanel>(Key.create("script.dependencies.panel")) private var FileEditor.notificationPanel: NewScriptDependenciesNotificationPanel? by UserDataProperty<FileEditor, NewScriptDependenciesNotificationPanel>(Key.create("script.dependencies.panel"))
private class NewScriptDependenciesNotificationPanel( private class NewScriptDependenciesNotificationPanel(
onClick: (ScriptCompilationConfigurationResult?) -> Unit, onClick: (ScriptCompilationConfigurationResult) -> Unit,
val compilationConfigurationResult: ScriptCompilationConfigurationResult?, val compilationConfigurationResult: ScriptCompilationConfigurationResult,
project: Project project: Project
) : EditorNotificationPanel() { ) : EditorNotificationPanel() {
@@ -15,15 +15,19 @@ import com.intellij.openapi.roots.ModuleRootEvent
import com.intellij.openapi.roots.ModuleRootListener import com.intellij.openapi.roots.ModuleRootListener
import com.intellij.openapi.roots.ModuleRootManager import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.roots.OrderRootType import com.intellij.openapi.roots.OrderRootType
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiElementFinder import com.intellij.psi.PsiElementFinder
import com.intellij.psi.PsiManager import com.intellij.psi.PsiManager
import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.search.NonClasspathDirectoriesScope import com.intellij.psi.search.NonClasspathDirectoriesScope
import com.intellij.util.containers.ConcurrentFactoryMap
import com.intellij.util.containers.SLRUMap import com.intellij.util.containers.SLRUMap
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.jetbrains.kotlin.idea.caches.project.getAllProjectSdks
import org.jetbrains.kotlin.idea.core.util.EDT import org.jetbrains.kotlin.idea.core.util.EDT
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
import java.util.concurrent.locks.ReentrantReadWriteLock import java.util.concurrent.locks.ReentrantReadWriteLock
@@ -51,45 +55,79 @@ class ScriptsCompilationConfigurationCache(private val project: Project) {
private val cacheLock = ReentrantReadWriteLock() private val cacheLock = ReentrantReadWriteLock()
private val scriptDependenciesCache = SLRUCacheWithLock<ScriptCompilationConfigurationResult>() private val scriptDependenciesCache = SLRUCacheWithLock<VirtualFile, ScriptCompilationConfigurationResult>()
private val scriptsModificationStampsCache = SLRUCacheWithLock<Long>() private val scriptsModificationStampsCache = SLRUCacheWithLock<KtFile, Long>()
operator fun get(virtualFile: VirtualFile): ScriptCompilationConfigurationResult? = scriptDependenciesCache.get(virtualFile) operator fun get(file: VirtualFile): ScriptCompilationConfigurationResult? = scriptDependenciesCache.get(file)
fun shouldRunDependenciesUpdate(file: VirtualFile): Boolean { fun shouldRunDependenciesUpdate(file: KtFile): Boolean {
return scriptsModificationStampsCache.replace(file, file.modificationStamp) != file.modificationStamp return scriptsModificationStampsCache.replace(file, file.modificationStamp) != file.modificationStamp
} }
private val scriptsDependenciesClasspathScopeCache = SLRUCacheWithLock<GlobalSearchScope>() private val scriptsDependenciesClasspathScopeCache: MutableMap<VirtualFile, GlobalSearchScope> =
ConcurrentFactoryMap.createWeakMap { file ->
val compilationConfiguration = getCachedConfigurationOrLoad(file)
?: return@createWeakMap null
fun scriptDependenciesClassFilesScope(file: VirtualFile): GlobalSearchScope {
return scriptsDependenciesClasspathScopeCache.getOrPut(file) {
val compilationConfiguration = scriptDependenciesCache.get(file)?.valueOrNull() ?: return@getOrPut GlobalSearchScope.EMPTY_SCOPE
val roots = compilationConfiguration.dependenciesClassPath val roots = compilationConfiguration.dependenciesClassPath
val sdk = getScriptSdk(file)
val sdk = ScriptDependenciesManager.getScriptSdk(compilationConfiguration)
@Suppress("FoldInitializerAndIfToElvis") @Suppress("FoldInitializerAndIfToElvis")
if (sdk == null) { if (sdk == null) {
return@getOrPut NonClasspathDirectoriesScope.compose(ScriptDependenciesManager.toVfsRoots(roots)) return@createWeakMap NonClasspathDirectoriesScope.compose(ScriptDependenciesManager.toVfsRoots(roots))
} }
return@getOrPut NonClasspathDirectoriesScope.compose( return@createWeakMap NonClasspathDirectoriesScope.compose(
sdk.rootProvider.getFiles(OrderRootType.CLASSES).toList() + sdk.rootProvider.getFiles(OrderRootType.CLASSES).toList() +
ScriptDependenciesManager.toVfsRoots(roots) ScriptDependenciesManager.toVfsRoots(roots)
) )
} }
fun scriptDependenciesClassFilesScope(file: VirtualFile): GlobalSearchScope {
return scriptsDependenciesClasspathScopeCache[file] ?: GlobalSearchScope.EMPTY_SCOPE
}
private val scriptsSdksCache: MutableMap<VirtualFile, Sdk?> =
ConcurrentFactoryMap.createWeakMap { file ->
val compilationConfiguration = getCachedConfigurationOrLoad(file)
return@createWeakMap getScriptSdk(compilationConfiguration) ?: ScriptDependenciesManager.getScriptDefaultSdk(project)
}
fun getScriptSdk(file: VirtualFile): Sdk? {
return scriptsSdksCache[file]
}
private fun getCachedConfigurationOrLoad(file: VirtualFile): ScriptCompilationConfigurationWrapper? {
val compilationConfiguration = scriptDependenciesCache.get(file)?.valueOrNull()
if (compilationConfiguration != null) {
return compilationConfiguration
}
val ktFile = PsiManager.getInstance(project).findFile(file) as? KtFile ?: return null
return ScriptDependenciesManager.getInstance(project).getRefinedCompilationConfiguration(ktFile)?.valueOrNull()
}
private fun getScriptSdk(compilationConfiguration: ScriptCompilationConfigurationWrapper?): Sdk? {
// workaround for mismatched gradle wrapper and plugin version
val javaHome = try {
compilationConfiguration?.javaHome?.let { VfsUtil.findFileByIoFile(it, true) }
} catch (e: Throwable) {
null
} ?: return null
return getAllProjectSdks().find { it.homeDirectory == javaHome }
} }
val allSdks by ClearableLazyValue(cacheLock) { val allSdks by ClearableLazyValue(cacheLock) {
scriptDependenciesCache.getAll() scriptDependenciesCache.getAll()
.mapNotNull { ScriptDependenciesManager.getInstance(project).getScriptSdk(it.key, project) } .mapNotNull { getScriptSdk(it.key) }
.distinct() .distinct()
} }
private val allNonIndexedSdks by ClearableLazyValue(cacheLock) { private val allNonIndexedSdks by ClearableLazyValue(cacheLock) {
scriptDependenciesCache.getAll() scriptDependenciesCache.getAll()
.mapNotNull { ScriptDependenciesManager.getInstance(project).getScriptSdk(it.key, project) } .mapNotNull { getScriptSdk(it.key) }
.filterNonModuleSdk() .filterNonModuleSdk()
.distinct() .distinct()
} }
@@ -164,7 +202,7 @@ class ScriptsCompilationConfigurationCache(private val project: Project) {
} }
fun hasNotCachedRoots(compilationConfiguration: ScriptCompilationConfigurationWrapper): Boolean { fun hasNotCachedRoots(compilationConfiguration: ScriptCompilationConfigurationWrapper): Boolean {
val scriptSdk = ScriptDependenciesManager.getScriptSdk(compilationConfiguration) ?: ScriptDependenciesManager.getScriptDefaultSdk(project) val scriptSdk = getScriptSdk(compilationConfiguration) ?: ScriptDependenciesManager.getScriptDefaultSdk(project)
return (scriptSdk != null && !allSdks.contains(scriptSdk)) || return (scriptSdk != null && !allSdks.contains(scriptSdk)) ||
!allDependenciesClassFiles.containsAll(ScriptDependenciesManager.toVfsRoots(compilationConfiguration.dependenciesClassPath)) || !allDependenciesClassFiles.containsAll(ScriptDependenciesManager.toVfsRoots(compilationConfiguration.dependenciesClassPath)) ||
!allDependenciesSources.containsAll(ScriptDependenciesManager.toVfsRoots(compilationConfiguration.dependenciesSources)) !allDependenciesSources.containsAll(ScriptDependenciesManager.toVfsRoots(compilationConfiguration.dependenciesSources))
@@ -176,23 +214,23 @@ class ScriptsCompilationConfigurationCache(private val project: Project) {
scriptDependenciesCache.clear() scriptDependenciesCache.clear()
scriptsModificationStampsCache.clear() scriptsModificationStampsCache.clear()
onChange(keys) updateHighlighting(keys)
} }
fun save(virtualFile: VirtualFile, new: ScriptCompilationConfigurationResult): Boolean { fun save(file: VirtualFile, new: ScriptCompilationConfigurationResult): Boolean {
val old = scriptDependenciesCache.replace(virtualFile, new) val old = scriptDependenciesCache.replace(file, new)
val changed = new != old val changed = new != old
if (changed) { if (changed) {
onChange(listOf(virtualFile)) onChange(listOf(file))
} }
return changed return changed
} }
fun delete(virtualFile: VirtualFile): Boolean { fun delete(file: VirtualFile): Boolean {
val changed = scriptDependenciesCache.remove(virtualFile) val changed = scriptDependenciesCache.remove(file)
if (changed) { if (changed) {
onChange(listOf(virtualFile)) onChange(listOf(file))
} }
return changed return changed
} }
@@ -226,20 +264,19 @@ private class ClearableLazyValue<in R, out T : Any>(
} }
} }
private class SLRUCacheWithLock<K, V> {
private class SLRUCacheWithLock<T> {
private val lock = ReentrantReadWriteLock() private val lock = ReentrantReadWriteLock()
val cache = SLRUMap<VirtualFile, T>( val cache = SLRUMap<K, V>(
ScriptsCompilationConfigurationCache.MAX_SCRIPTS_CACHED, ScriptsCompilationConfigurationCache.MAX_SCRIPTS_CACHED,
ScriptsCompilationConfigurationCache.MAX_SCRIPTS_CACHED ScriptsCompilationConfigurationCache.MAX_SCRIPTS_CACHED
) )
fun get(value: VirtualFile): T? = lock.write { fun get(value: K): V? = lock.write {
cache[value] cache[value]
} }
fun getOrPut(key: VirtualFile, defaultValue: () -> T): T = lock.write { fun getOrPut(key: K, defaultValue: () -> V): V = lock.write {
val value = cache.get(key) val value = cache.get(key)
return if (value == null) { return if (value == null) {
val answer = defaultValue() val answer = defaultValue()
@@ -250,11 +287,11 @@ private class SLRUCacheWithLock<T> {
} }
} }
fun remove(file: VirtualFile) = lock.write { fun remove(file: K) = lock.write {
cache.remove(file) cache.remove(file)
} }
fun getAll(): Collection<Map.Entry<VirtualFile, T>> = lock.write { fun getAll(): Collection<Map.Entry<K, V>> = lock.write {
cache.entrySet() cache.entrySet()
} }
@@ -262,10 +299,9 @@ private class SLRUCacheWithLock<T> {
cache.clear() cache.clear()
} }
fun replace(file: VirtualFile, value: T): T? = lock.write { fun replace(file: K, value: V): V? = lock.write {
val old = get(file) val old = get(file)
cache.put(file, value) cache.put(file, value)
old old
} }
} }
@@ -39,13 +39,9 @@ import org.jetbrains.kotlin.idea.core.script.dependencies.SyncScriptDependencies
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.NotNullableUserDataProperty import org.jetbrains.kotlin.psi.NotNullableUserDataProperty
import org.jetbrains.kotlin.scripting.definitions.KotlinScriptDefinition
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition
import org.jetbrains.kotlin.scripting.definitions.isNonScript import org.jetbrains.kotlin.scripting.definitions.isNonScript
import org.jetbrains.kotlin.scripting.resolve.LegacyResolverWrapper
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult
import kotlin.script.experimental.dependencies.AsyncDependenciesResolver
class ScriptsCompilationConfigurationUpdater( class ScriptsCompilationConfigurationUpdater(
private val project: Project, private val project: Project,
@@ -65,8 +61,10 @@ class ScriptsCompilationConfigurationUpdater(
listenForChangesInScripts() listenForChangesInScripts()
} }
fun getCurrentCompilationConfiguration(file: VirtualFile): ScriptCompilationConfigurationResult? { fun getCurrentCompilationConfiguration(file: KtFile): ScriptCompilationConfigurationResult? {
cache[file]?.let { return it } val virtualFile = file.originalFile.virtualFile
cache[virtualFile]?.let { return it }
// Try-catch block is a temporary workaround for ProcessCanceledException // Try-catch block is a temporary workaround for ProcessCanceledException
// that may happen during VirtualFile to PsiFile transformation // that may happen during VirtualFile to PsiFile transformation
@@ -76,10 +74,10 @@ class ScriptsCompilationConfigurationUpdater(
makeRootsChangeIfNeeded() makeRootsChangeIfNeeded()
} }
return cache[file] return cache[virtualFile]
} }
fun updateDependenciesIfNeeded(files: List<VirtualFile>): Boolean { fun updateDependenciesIfNeeded(files: List<KtFile>): Boolean {
if (!ScriptDefinitionsManager.getInstance(project).isReady()) return false if (!ScriptDefinitionsManager.getInstance(project).isReady()) return false
var wasDependenciesUpdateStarted = false var wasDependenciesUpdateStarted = false
@@ -97,11 +95,11 @@ class ScriptsCompilationConfigurationUpdater(
return wasDependenciesUpdateStarted return wasDependenciesUpdateStarted
} }
private fun updateDependencies(file: VirtualFile) { private fun updateDependencies(file: KtFile) {
if (!ScriptDefinitionsManager.getInstance(project).isReady()) return if (!ScriptDefinitionsManager.getInstance(project).isReady()) return
if (!cache.shouldRunDependenciesUpdate(file)) return if (!cache.shouldRunDependenciesUpdate(file)) return
val scriptDefinition = file.findScriptDefinition(project) ?: return val scriptDefinition = file.findScriptDefinition() ?: return
loaders.filter { it.isApplicable(file, scriptDefinition) }.forEach { it.loadDependencies(file, scriptDefinition) } loaders.filter { it.isApplicable(file, scriptDefinition) }.forEach { it.loadDependencies(file, scriptDefinition) }
} }
@@ -122,24 +120,18 @@ class ScriptsCompilationConfigurationUpdater(
} }
private fun runScriptDependenciesUpdateIfNeeded(file: VirtualFile) { private fun runScriptDependenciesUpdateIfNeeded(file: VirtualFile) {
if (!shouldStartUpdate(file)) return val ktFile = getKtFileToStartConfigurationUpdate(file) ?: return
updateDependencies(file) updateDependencies(ktFile)
makeRootsChangeIfNeeded() makeRootsChangeIfNeeded()
} }
}) })
EditorFactory.getInstance().eventMulticaster.addDocumentListener(object : DocumentListener { EditorFactory.getInstance().eventMulticaster.addDocumentListener(object : DocumentListener {
override fun documentChanged(event: DocumentEvent) { override fun documentChanged(event: DocumentEvent) {
val document = event.document val document = event.document
val file = FileDocumentManager.getInstance().getFile(document)?.takeIf { it.isInLocalFileSystem } ?: return val file = FileDocumentManager.getInstance().getFile(document)?.takeIf { it.isInLocalFileSystem } ?: return
if (!file.isValid) { val ktFile = getKtFileToStartConfigurationUpdate(file) ?: return
cache.delete(file)
return
}
if (!shouldStartUpdate(file)) return
// only update dependencies for scripts that were touched recently // only update dependencies for scripts that were touched recently
if (cache[file] == null) { if (cache[file] == null) {
@@ -151,7 +143,7 @@ class ScriptsCompilationConfigurationUpdater(
scriptsQueue.addRequest( scriptsQueue.addRequest(
{ {
FileDocumentManager.getInstance().saveDocument(document) FileDocumentManager.getInstance().saveDocument(document)
updateDependencies(file) updateDependencies(ktFile)
makeRootsChangeIfNeeded() makeRootsChangeIfNeeded()
}, },
scriptChangesListenerDelay, scriptChangesListenerDelay,
@@ -161,31 +153,30 @@ class ScriptsCompilationConfigurationUpdater(
}, project.messageBus.connect()) }, project.messageBus.connect())
} }
private fun shouldStartUpdate(file: VirtualFile): Boolean { private fun getKtFileToStartConfigurationUpdate(file: VirtualFile): KtFile? {
if (project.isDisposed || !file.isValid || file.isNonScript()) { if (project.isDisposed || !file.isValid || file.isNonScript()) {
return false return null
} }
if ( if (
ApplicationManager.getApplication().isUnitTestMode && ApplicationManager.getApplication().isUnitTestMode &&
ApplicationManager.getApplication().isScriptDependenciesUpdaterDisabled == true ApplicationManager.getApplication().isScriptDependenciesUpdaterDisabled == true
) { ) {
return false return null
} }
val ktFile = PsiManager.getInstance(project).findFile(file) as? KtFile ?: return false val ktFile = PsiManager.getInstance(project).findFile(file) as? KtFile ?: return null
return ProjectRootsUtil.isInProjectSource(ktFile, includeScriptsOutsideSourceRoots = true) if (ProjectRootsUtil.isInProjectSource(ktFile, includeScriptsOutsideSourceRoots = true)) {
return ktFile
}
return null
} }
private fun areDependenciesCached(file: VirtualFile): Boolean { private fun areDependenciesCached(file: VirtualFile): Boolean {
return cache[file] != null || file.scriptDependencies != null return cache[file] != null || file.scriptDependencies != null || file.scriptCompilationConfiguration != null
} }
fun isAsyncDependencyResolver(scriptDef: ScriptDefinition): Boolean =
scriptDef.asLegacyOrNull<KotlinScriptDefinition>()?.dependencyResolver?.let {
it is AsyncDependenciesResolver || it is LegacyResolverWrapper
} ?: false
companion object { companion object {
@JvmStatic @JvmStatic
fun getInstance(project: Project): ScriptsCompilationConfigurationUpdater = fun getInstance(project: Project): ScriptsCompilationConfigurationUpdater =
@@ -194,11 +185,6 @@ class ScriptsCompilationConfigurationUpdater(
fun areDependenciesCached(file: KtFile): Boolean { fun areDependenciesCached(file: KtFile): Boolean {
return getInstance(file.project).areDependenciesCached(file.virtualFile) return getInstance(file.project).areDependenciesCached(file.virtualFile)
} }
fun isAsyncDependencyResolver(file: KtFile): Boolean {
val scriptDefinition = file.virtualFile.findScriptDefinition(file.project) ?: return false
return getInstance(file.project).isAsyncDependencyResolver(scriptDefinition)
}
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors. * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/ */
@@ -10,13 +10,12 @@ import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.Task import com.intellij.openapi.progress.Task
import com.intellij.openapi.progress.util.BackgroundTaskUtil import com.intellij.openapi.progress.util.BackgroundTaskUtil
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import kotlinx.coroutines.Runnable import kotlinx.coroutines.Runnable
import org.jetbrains.kotlin.idea.core.script.ScriptsCompilationConfigurationUpdater
import org.jetbrains.kotlin.idea.core.script.settings.KotlinScriptingSettings import org.jetbrains.kotlin.idea.core.script.settings.KotlinScriptingSettings
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition
import org.jetbrains.kotlin.scripting.resolve.VirtualFileScriptSource import org.jetbrains.kotlin.scripting.resolve.KtFileScriptSource
import org.jetbrains.kotlin.scripting.resolve.refineScriptCompilationConfiguration import org.jetbrains.kotlin.scripting.resolve.refineScriptCompilationConfiguration
import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.locks.ReentrantReadWriteLock import java.util.concurrent.locks.ReentrantReadWriteLock
@@ -30,14 +29,14 @@ class AsyncScriptDependenciesLoader internal constructor(project: Project) : Scr
private var backgroundTasksQueue: LoaderBackgroundTask? = null private var backgroundTasksQueue: LoaderBackgroundTask? = null
override fun isApplicable( override fun isApplicable(
file: VirtualFile, file: KtFile,
scriptDefinition: ScriptDefinition scriptDefinition: ScriptDefinition
): Boolean { ): Boolean {
return ScriptsCompilationConfigurationUpdater.getInstance(project).isAsyncDependencyResolver(scriptDefinition) return isAsyncDependencyResolver(scriptDefinition)
} }
override fun loadDependencies( override fun loadDependencies(
file: VirtualFile, file: KtFile,
scriptDefinition: ScriptDefinition scriptDefinition: ScriptDefinition
) { ) {
lock.write { lock.write {
@@ -74,20 +73,20 @@ class AsyncScriptDependenciesLoader internal constructor(project: Project) : Scr
return false return false
} }
private fun runDependenciesUpdate(file: VirtualFile) { private fun runDependenciesUpdate(file: KtFile) {
val scriptDef = file.findScriptDefinition(project) ?: return val scriptDef = file.findScriptDefinition() ?: return
debug(file) { "start async dependencies loading" } debug(file) { "start async dependencies loading" }
val result = refineScriptCompilationConfiguration(VirtualFileScriptSource(file), scriptDef, project) val result = refineScriptCompilationConfiguration(KtFileScriptSource(file), scriptDef, project)
debug(file) { "finish async dependencies loading" } debug(file) { "finish async dependencies loading" }
processRefinedConfiguration(result, file) processRefinedConfiguration(result, file.originalFile.virtualFile)
} }
private inner class LoaderBackgroundTask { private inner class LoaderBackgroundTask {
private val sequenceOfFiles: ConcurrentLinkedQueue<VirtualFile> = ConcurrentLinkedQueue() private val sequenceOfFiles: ConcurrentLinkedQueue<KtFile> = ConcurrentLinkedQueue()
private var forceStop : Boolean = false private var forceStop : Boolean = false
private var startedSilently : Boolean = false private var startedSilently : Boolean = false
@@ -124,7 +123,7 @@ class AsyncScriptDependenciesLoader internal constructor(project: Project) : Scr
}.queue() }.queue()
} }
fun addTask(file: VirtualFile) { fun addTask(file: KtFile) {
if (sequenceOfFiles.contains(file)) return if (sequenceOfFiles.contains(file)) return
debug(file) { "added to update queue" } debug(file) { "added to update queue" }
@@ -9,39 +9,41 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.idea.core.script.scriptCompilationConfiguration import org.jetbrains.kotlin.idea.core.script.scriptCompilationConfiguration
import org.jetbrains.kotlin.idea.core.script.scriptDependencies import org.jetbrains.kotlin.idea.core.script.scriptDependencies
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
import org.jetbrains.kotlin.scripting.resolve.KtFileScriptSource
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
import org.jetbrains.kotlin.scripting.resolve.VirtualFileScriptSource
import kotlin.script.experimental.api.asSuccess import kotlin.script.experimental.api.asSuccess
// TODO: rename and provide alias for compatibility - this is not only about dependencies anymore // TODO: rename and provide alias for compatibility - this is not only about dependencies anymore
class FromFileAttributeScriptDependenciesLoader(project: Project) : ScriptDependenciesLoader(project) { class FromFileAttributeScriptDependenciesLoader(project: Project) : ScriptDependenciesLoader(project) {
override fun isApplicable( override fun isApplicable(
file: VirtualFile, file: KtFile,
scriptDefinition: ScriptDefinition scriptDefinition: ScriptDefinition
): Boolean { ): Boolean {
return file.scriptDependencies != null || file.scriptCompilationConfiguration != null return file.scriptDependencies != null || file.scriptCompilationConfiguration != null
} }
override fun loadDependencies( override fun loadDependencies(
file: VirtualFile, file: KtFile,
scriptDefinition: ScriptDefinition scriptDefinition: ScriptDefinition
) { ) {
file.scriptCompilationConfiguration?.let { val virtualFile = file.originalFile.virtualFile
ScriptCompilationConfigurationWrapper.FromCompilationConfiguration(VirtualFileScriptSource(file), it).apply { virtualFile.scriptCompilationConfiguration?.let {
debug(file) { "refined configuration from fileAttributes = $it" } ScriptCompilationConfigurationWrapper.FromCompilationConfiguration(KtFileScriptSource(file), it).apply {
debug(virtualFile) { "refined configuration from fileAttributes = $it" }
} }
} ?: file.scriptDependencies?.let { } ?: virtualFile.scriptDependencies?.let {
ScriptCompilationConfigurationWrapper.FromLegacy(VirtualFileScriptSource(file), it, scriptDefinition).apply { ScriptCompilationConfigurationWrapper.FromLegacy(KtFileScriptSource(file), it, scriptDefinition).apply {
debug(file) { "dependencies from fileAttributes = $it" } debug(virtualFile) { "dependencies from fileAttributes = $it" }
} }
}?.let { }?.let {
if (areDependenciesValid(file, it)) { if (areDependenciesValid(virtualFile, it)) {
saveToCache(file, it.asSuccess(), skipSaveToAttributes = true) saveToCache(virtualFile, it.asSuccess(), skipSaveToAttributes = true)
} else { } else {
file.scriptCompilationConfiguration = null virtualFile.scriptCompilationConfiguration = null
file.scriptDependencies = null virtualFile.scriptDependencies = null
} }
} }
} }
@@ -18,10 +18,13 @@ package org.jetbrains.kotlin.idea.core.script.dependencies
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiManager
import com.intellij.psi.ResolveScopeProvider import com.intellij.psi.ResolveScopeProvider
import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.core.script.ScriptDependenciesManager import org.jetbrains.kotlin.idea.core.script.ScriptDependenciesManager
import org.jetbrains.kotlin.idea.core.script.StandardIdeScriptDefinition import org.jetbrains.kotlin.idea.core.script.StandardIdeScriptDefinition
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition
import org.jetbrains.kotlin.scripting.resolve.KotlinScriptDefinitionFromAnnotatedTemplate import org.jetbrains.kotlin.scripting.resolve.KotlinScriptDefinitionFromAnnotatedTemplate
@@ -33,7 +36,10 @@ class KotlinScriptResolveScopeProvider : ResolveScopeProvider() {
} }
override fun getResolveScope(file: VirtualFile, project: Project): GlobalSearchScope? { override fun getResolveScope(file: VirtualFile, project: Project): GlobalSearchScope? {
val scriptDefinition = file.findScriptDefinition(project) if (file.fileType != KotlinFileType.INSTANCE) return null
val ktFile = PsiManager.getInstance(project).findFile(file) as? KtFile ?: return null
val scriptDefinition = ktFile.findScriptDefinition()
return when { return when {
scriptDefinition == null -> null scriptDefinition == null -> null
// This is a workaround for completion in scripts and REPL to provide module dependencies // This is a workaround for completion in scripts and REPL to provide module dependencies
@@ -6,27 +6,32 @@
package org.jetbrains.kotlin.idea.core.script.dependencies package org.jetbrains.kotlin.idea.core.script.dependencies
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiManager
import org.jetbrains.kotlin.idea.core.script.ScriptDependenciesManager import org.jetbrains.kotlin.idea.core.script.ScriptDependenciesManager
import org.jetbrains.kotlin.idea.highlighter.OutsidersPsiFileSupportUtils import org.jetbrains.kotlin.idea.highlighter.OutsidersPsiFileSupportUtils
import org.jetbrains.kotlin.idea.highlighter.OutsidersPsiFileSupportWrapper import org.jetbrains.kotlin.idea.highlighter.OutsidersPsiFileSupportWrapper
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
class OutsiderFileDependenciesLoader(project: Project) : ScriptDependenciesLoader(project) { class OutsiderFileDependenciesLoader(project: Project) : ScriptDependenciesLoader(project) {
override fun isApplicable( override fun isApplicable(
file: VirtualFile, file: KtFile,
scriptDefinition: ScriptDefinition scriptDefinition: ScriptDefinition
): Boolean { ): Boolean {
return OutsidersPsiFileSupportWrapper.isOutsiderFile(file) val virtualFile = file.virtualFile ?: return false
return OutsidersPsiFileSupportWrapper.isOutsiderFile(virtualFile)
} }
override fun loadDependencies( override fun loadDependencies(
file: VirtualFile, file: KtFile,
scriptDefinition: ScriptDefinition scriptDefinition: ScriptDefinition
) { ) {
val fileOrigin = OutsidersPsiFileSupportUtils.getOutsiderFileOrigin(project, file) ?: return val virtualFile = file.virtualFile ?: return
val compilationConfiguration = ScriptDependenciesManager.getInstance(project).getRefinedCompilationConfiguration(fileOrigin) ?: return val fileOrigin = OutsidersPsiFileSupportUtils.getOutsiderFileOrigin(project, virtualFile) ?: return
saveToCache(file, compilationConfiguration) val psiFileOrigin = PsiManager.getInstance(project).findFile(fileOrigin) as? KtFile ?: return
val compilationConfiguration =
ScriptDependenciesManager.getInstance(project).getRefinedCompilationConfiguration(psiFileOrigin) ?: return
saveToCache(virtualFile, compilationConfiguration)
} }
override fun shouldShowNotification(): Boolean = false override fun shouldShowNotification(): Boolean = false
@@ -15,18 +15,22 @@ import com.intellij.openapi.util.EmptyRunnable
import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.idea.core.script.* import org.jetbrains.kotlin.idea.core.script.*
import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.definitions.KotlinScriptDefinition
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
import org.jetbrains.kotlin.scripting.resolve.LegacyResolverWrapper
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
import org.jetbrains.kotlin.scripting.resolve.ScriptReportSink import org.jetbrains.kotlin.scripting.resolve.ScriptReportSink
import kotlin.script.experimental.api.ResultWithDiagnostics import kotlin.script.experimental.api.ResultWithDiagnostics
import kotlin.script.experimental.api.valueOrNull import kotlin.script.experimental.api.valueOrNull
import kotlin.script.experimental.dependencies.AsyncDependenciesResolver
// TODO: rename and provide alias for compatibility - this is not only about dependencies anymore // TODO: rename and provide alias for compatibility - this is not only about dependencies anymore
abstract class ScriptDependenciesLoader(protected val project: Project) { abstract class ScriptDependenciesLoader(protected val project: Project) {
abstract fun isApplicable(file: VirtualFile, scriptDefinition: ScriptDefinition): Boolean abstract fun isApplicable(file: KtFile, scriptDefinition: ScriptDefinition): Boolean
abstract fun loadDependencies(file: VirtualFile, scriptDefinition: ScriptDefinition) abstract fun loadDependencies(file: KtFile, scriptDefinition: ScriptDefinition)
protected abstract fun shouldShowNotification(): Boolean protected abstract fun shouldShowNotification(): Boolean
@@ -138,13 +142,23 @@ abstract class ScriptDependenciesLoader(protected val project: Project) {
return true return true
} }
companion object { protected fun isAsyncDependencyResolver(scriptDef: ScriptDefinition): Boolean =
private val LOG = Logger.getInstance("#org.jetbrains.kotlin.idea.script") scriptDef.asLegacyOrNull<KotlinScriptDefinition>()?.dependencyResolver?.let {
it is AsyncDependenciesResolver || it is LegacyResolverWrapper
} ?: false
internal fun debug(file: VirtualFile? = null, message: () -> String) { }
if (LOG.isDebugEnabled) {
LOG.debug("[KOTLIN SCRIPT] " + (file?.let { "file = ${file.path}, " } ?: "") + message()) private val LOG = Logger.getInstance("#org.jetbrains.kotlin.idea.script")
}
} internal fun debug(file: VirtualFile? = null, message: () -> String) {
if (LOG.isDebugEnabled) {
LOG.debug("[KOTLIN SCRIPT] ${file?.path} " + message())
}
}
internal fun debug(file: KtFile, message: () -> String) {
if (LOG.isDebugEnabled) {
LOG.debug("[KOTLIN SCRIPT] ${file.originalFile.virtualFile?.path} " + message())
} }
} }
@@ -6,28 +6,27 @@
package org.jetbrains.kotlin.idea.core.script.dependencies package org.jetbrains.kotlin.idea.core.script.dependencies
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.idea.core.script.ScriptsCompilationConfigurationUpdater
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
import org.jetbrains.kotlin.scripting.resolve.VirtualFileScriptSource import org.jetbrains.kotlin.scripting.resolve.KtFileScriptSource
import org.jetbrains.kotlin.scripting.resolve.refineScriptCompilationConfiguration import org.jetbrains.kotlin.scripting.resolve.refineScriptCompilationConfiguration
class SyncScriptDependenciesLoader(project: Project) : ScriptDependenciesLoader(project) { class SyncScriptDependenciesLoader(project: Project) : ScriptDependenciesLoader(project) {
override fun isApplicable( override fun isApplicable(
file: VirtualFile, file: KtFile,
scriptDefinition: ScriptDefinition scriptDefinition: ScriptDefinition
): Boolean { ): Boolean {
return !ScriptsCompilationConfigurationUpdater.getInstance(project).isAsyncDependencyResolver(scriptDefinition) return !isAsyncDependencyResolver(scriptDefinition)
} }
override fun loadDependencies( override fun loadDependencies(
file: VirtualFile, file: KtFile,
scriptDefinition: ScriptDefinition scriptDefinition: ScriptDefinition
) { ) {
debug(file) { "start sync dependencies loading" } debug(file) { "start sync dependencies loading" }
val result = refineScriptCompilationConfiguration(VirtualFileScriptSource(file), scriptDefinition, project) val result = refineScriptCompilationConfiguration(KtFileScriptSource(file), scriptDefinition, project)
debug(file) { "finish sync dependencies loading" } debug(file) { "finish sync dependencies loading" }
processRefinedConfiguration(result, file) processRefinedConfiguration(result, file.originalFile.virtualFile)
} }
override fun shouldShowNotification(): Boolean = false override fun shouldShowNotification(): Boolean = false
@@ -48,7 +48,7 @@ class GradleBuildFileHighlightingTest : GradleImportingTestCase() {
val psiFile = PsiManager.getInstance(myProject).findFile(file) as? KtFile val psiFile = PsiManager.getInstance(myProject).findFile(file) as? KtFile
?: error("Couldn't find psiFile for virtual file: ${file.canonicalPath}") ?: error("Couldn't find psiFile for virtual file: ${file.canonicalPath}")
ScriptDependenciesManager.updateScriptDependenciesSynchronously(file, myProject) ScriptDependenciesManager.updateScriptDependenciesSynchronously(psiFile, myProject)
val bindingContext = psiFile.analyzeWithContent() val bindingContext = psiFile.analyzeWithContent()
@@ -166,7 +166,7 @@ class KtScratchExecutionSession(
} }
ScriptDependenciesManager.getInstance(originalFile.project) ScriptDependenciesManager.getInstance(originalFile.project)
.getRefinedCompilationConfiguration(originalFile.virtualFile)?.valueOrNull()?.let { .getRefinedCompilationConfiguration(originalFile)?.valueOrNull()?.let {
javaParameters.classPath.addAll(it.dependenciesClassPath.map { f -> f.absolutePath }) javaParameters.classPath.addAll(it.dependenciesClassPath.map { f -> f.absolutePath })
} }
@@ -65,8 +65,9 @@ class ImportInsertHelperImpl(private val project: Project) : ImportInsertHelper(
val allDefaultImports = platform.findAnalyzerServices.getDefaultImports(languageVersionSettings, includeLowPriorityImports = true) val allDefaultImports = platform.findAnalyzerServices.getDefaultImports(languageVersionSettings, includeLowPriorityImports = true)
val scriptExtraImports = contextFile.takeIf { it.isScript() }?.let { ktFile -> val scriptExtraImports = contextFile.takeIf { it.isScript() }?.let { ktFile ->
val scriptDependencies = val scriptDependencies = ScriptDependenciesProvider.getInstance(ktFile.project)
ScriptDependenciesProvider.getInstance(ktFile.project)?.getScriptConfigurationResult(ktFile.originalFile)?.valueOrNull() ?.getScriptConfigurationResult(ktFile.originalFile as KtFile)
?.valueOrNull()
scriptDependencies?.defaultImports?.map { ImportPath.fromString(it) } scriptDependencies?.defaultImports?.map { ImportPath.fromString(it) }
}.orEmpty() }.orEmpty()
@@ -12,10 +12,7 @@ import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.vfs.VfsUtil import com.intellij.openapi.vfs.VfsUtil
import com.intellij.psi.PsiComment import com.intellij.psi.*
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiJavaFile
import com.intellij.psi.codeStyle.JavaCodeStyleManager import com.intellij.psi.codeStyle.JavaCodeStyleManager
import com.intellij.psi.codeStyle.VariableKind import com.intellij.psi.codeStyle.VariableKind
import com.intellij.refactoring.BaseRefactoringProcessor.ConflictsInTestsException import com.intellij.refactoring.BaseRefactoringProcessor.ConflictsInTestsException
@@ -32,8 +29,8 @@ import com.intellij.testFramework.fixtures.CodeInsightTestFixture
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.idea.KotlinFileType import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.core.util.CodeInsightUtils
import org.jetbrains.kotlin.idea.core.script.ScriptDependenciesManager import org.jetbrains.kotlin.idea.core.script.ScriptDependenciesManager
import org.jetbrains.kotlin.idea.core.util.CodeInsightUtils
import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively
import org.jetbrains.kotlin.idea.refactoring.chooseMembers import org.jetbrains.kotlin.idea.refactoring.chooseMembers
import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.ExtractSuperInfo import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.ExtractSuperInfo
@@ -325,7 +322,9 @@ abstract class AbstractExtractionTest : KotlinLightCodeInsightFixtureTestCase()
PluginTestCaseBase.addJdk(myFixture.projectDisposable, PluginTestCaseBase::mockJdk) PluginTestCaseBase.addJdk(myFixture.projectDisposable, PluginTestCaseBase::mockJdk)
if (mainFile.extension == KotlinParserDefinition.STD_SCRIPT_SUFFIX) { if (mainFile.extension == KotlinParserDefinition.STD_SCRIPT_SUFFIX) {
ScriptDependenciesManager.updateScriptDependenciesSynchronously(VfsUtil.findFileByIoFile(mainFile, true)!!, project) val virtualFile = VfsUtil.findFileByIoFile(mainFile, true)!!
val ktFile = PsiManager.getInstance(project).findFile(virtualFile)!!
ScriptDependenciesManager.updateScriptDependenciesSynchronously(ktFile, project)
} }
fixture.testDataPath = "${KotlinTestUtils.getHomeDirectory()}/${mainFile.parent}" fixture.testDataPath = "${KotlinTestUtils.getHomeDirectory()}/${mainFile.parent}"
@@ -71,7 +71,7 @@ class StandaloneScriptRunConfigurationTest : KotlinCodeInsightTestCase() {
fun testOnFileMoveWithDefaultWorkingDir() { fun testOnFileMoveWithDefaultWorkingDir() {
configureByFile("move/script.kts") configureByFile("move/script.kts")
ScriptDependenciesManager.updateScriptDependenciesSynchronously(myFile.virtualFile, project) ScriptDependenciesManager.updateScriptDependenciesSynchronously(myFile, project)
val script = KotlinScriptFqnIndex.instance.get("foo.Script", project, project.allScope()).single() val script = KotlinScriptFqnIndex.instance.get("foo.Script", project, project.allScope()).single()
val runConfiguration = createConfigurationFromElement(script, save = true) as KotlinStandaloneScriptRunConfiguration val runConfiguration = createConfigurationFromElement(script, save = true) as KotlinStandaloneScriptRunConfiguration
@@ -98,7 +98,7 @@ class StandaloneScriptRunConfigurationTest : KotlinCodeInsightTestCase() {
fun testOnFileMoveWithNonDefaultWorkingDir() { fun testOnFileMoveWithNonDefaultWorkingDir() {
configureByFile("move/script.kts") configureByFile("move/script.kts")
ScriptDependenciesManager.updateScriptDependenciesSynchronously(myFile.virtualFile, project) ScriptDependenciesManager.updateScriptDependenciesSynchronously(myFile, project)
val script = KotlinScriptFqnIndex.instance.get("foo.Script", project, project.allScope()).single() val script = KotlinScriptFqnIndex.instance.get("foo.Script", project, project.allScope()).single()
val runConfiguration = createConfigurationFromElement(script, save = true) as KotlinStandaloneScriptRunConfiguration val runConfiguration = createConfigurationFromElement(script, save = true) as KotlinStandaloneScriptRunConfiguration
@@ -204,7 +204,7 @@ abstract class AbstractScratchRunActionTest : FileEditorManagerTestCase() {
myFixture.openFileInEditor(scratchVirtualFile) myFixture.openFileInEditor(scratchVirtualFile)
ScriptDependenciesManager.updateScriptDependenciesSynchronously(scratchVirtualFile, project) ScriptDependenciesManager.updateScriptDependenciesSynchronously(myFixture.file, project)
val scratchFileEditor = getScratchEditorForSelectedFile(myManager, myFixture.file.virtualFile) val scratchFileEditor = getScratchEditorForSelectedFile(myManager, myFixture.file.virtualFile)
?: error("Couldn't find scratch file") ?: error("Couldn't find scratch file")
@@ -217,7 +217,7 @@ abstract class AbstractScratchRunActionTest : FileEditorManagerTestCase() {
protected fun configureWorksheetByText(name: String, text: String): ScratchFile { protected fun configureWorksheetByText(name: String, text: String): ScratchFile {
val worksheetFile = myFixture.configureByText(name, text).virtualFile val worksheetFile = myFixture.configureByText(name, text).virtualFile
ScriptDependenciesManager.updateScriptDependenciesSynchronously(worksheetFile, project) ScriptDependenciesManager.updateScriptDependenciesSynchronously(myFixture.file, project)
val scratchFileEditor = getScratchEditorForSelectedFile(myManager, myFixture.file.virtualFile) val scratchFileEditor = getScratchEditorForSelectedFile(myManager, myFixture.file.virtualFile)
?: error("Couldn't find scratch panel") ?: error("Couldn't find scratch panel")
@@ -37,7 +37,7 @@ abstract class AbstractScratchLineMarkersTest : FileEditorManagerTestCase() {
myFixture.openFileInEditor(scratchVirtualFile) myFixture.openFileInEditor(scratchVirtualFile)
ScriptDependenciesManager.updateScriptDependenciesSynchronously(scratchVirtualFile, project) ScriptDependenciesManager.updateScriptDependenciesSynchronously(myFixture.file, project)
val scratchFileEditor = getScratchEditorForSelectedFile(FileEditorManager.getInstance(project), myFixture.file.virtualFile) val scratchFileEditor = getScratchEditorForSelectedFile(FileEditorManager.getInstance(project), myFixture.file.virtualFile)
?: error("Couldn't find scratch panel") ?: error("Couldn't find scratch panel")
@@ -35,6 +35,7 @@ import org.jetbrains.kotlin.idea.highlighter.KotlinHighlightingUtil
import org.jetbrains.kotlin.idea.navigation.GotoCheck import org.jetbrains.kotlin.idea.navigation.GotoCheck
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.test.InTextDirectivesUtils import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.jetbrains.kotlin.test.KotlinTestUtils import org.jetbrains.kotlin.test.KotlinTestUtils
import org.jetbrains.kotlin.test.MockLibraryUtil import org.jetbrains.kotlin.test.MockLibraryUtil
@@ -78,7 +79,7 @@ abstract class AbstractScriptConfigurationHighlightingTest : AbstractScriptConfi
} }
}) })
updateScriptDependenciesSynchronously(myFile.virtualFile, project) updateScriptDependenciesSynchronously(myFile, project)
checkHighlighting(editor, false, false) checkHighlighting(editor, false, false)
} }
} }
@@ -134,7 +135,7 @@ abstract class AbstractScriptDefinitionsOrderTest : AbstractScriptConfigurationT
} }
ScriptDefinitionsManager.getInstance(project).reorderScriptDefinitions() ScriptDefinitionsManager.getInstance(project).reorderScriptDefinitions()
updateScriptDependenciesSynchronously(myFile.virtualFile, project) updateScriptDependenciesSynchronously(myFile, project)
checkHighlighting(editor, false, false) checkHighlighting(editor, false, false)
} }
@@ -345,13 +346,13 @@ abstract class AbstractScriptConfigurationTest : KotlinCompletionTestCase() {
if (script == null) error("Test file with script couldn't be found in test project") if (script == null) error("Test file with script couldn't be found in test project")
configureByExistingFile(script) configureByExistingFile(script)
updateScriptDependenciesSynchronously(script, project) updateScriptDependenciesSynchronously(myFile, project)
VfsUtil.markDirtyAndRefresh(false, true, true, project.baseDir) VfsUtil.markDirtyAndRefresh(false, true, true, project.baseDir)
// This is needed because updateScriptDependencies invalidates psiFile that was stored in myFile field // This is needed because updateScriptDependencies invalidates psiFile that was stored in myFile field
myFile = psiManager.findFile(script) myFile = psiManager.findFile(script)
val isFatalErrorPresent = IdeScriptReportSink.getReports(script).any { it.severity == ScriptDiagnostic.Severity.FATAL } val isFatalErrorPresent = IdeScriptReportSink.getReports(myFile as KtFile).any { it.severity == ScriptDiagnostic.Severity.FATAL }
assert(isFatalErrorPresent || KotlinHighlightingUtil.shouldHighlight(myFile)) { assert(isFatalErrorPresent || KotlinHighlightingUtil.shouldHighlight(myFile)) {
"Highlighting is switched off for ${myFile.virtualFile.path}" "Highlighting is switched off for ${myFile.virtualFile.path}"
} }
@@ -9,24 +9,29 @@ import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiFile import com.intellij.psi.PsiFile
import com.intellij.psi.PsiManager
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult
import kotlin.script.experimental.api.valueOrNull import kotlin.script.experimental.api.valueOrNull
import kotlin.script.experimental.dependencies.ScriptDependencies import kotlin.script.experimental.dependencies.ScriptDependencies
interface ScriptDependenciesProvider { open class ScriptDependenciesProvider constructor(
protected val project: Project
) {
@Deprecated("Migrating to configuration refinement", level = DeprecationLevel.ERROR) @Deprecated("Migrating to configuration refinement", level = DeprecationLevel.ERROR)
fun getScriptDependencies(file: VirtualFile): ScriptDependencies? = fun getScriptDependencies(file: VirtualFile): ScriptDependencies? {
getScriptConfigurationResult(file)?.valueOrNull()?.legacyDependencies val ktFile = PsiManager.getInstance(project).findFile(file) as? KtFile ?: return null
return getScriptConfigurationResult(ktFile)?.valueOrNull()?.legacyDependencies
}
@Deprecated("Migrating to configuration refinement", level = DeprecationLevel.ERROR) @Deprecated("Migrating to configuration refinement", level = DeprecationLevel.ERROR)
fun getScriptDependencies(file: PsiFile): ScriptDependencies? = fun getScriptDependencies(file: PsiFile): ScriptDependencies? {
getScriptConfigurationResult(file.virtualFile ?: file.originalFile.virtualFile)?.valueOrNull()?.legacyDependencies if (file !is KtFile) return null
return getScriptConfigurationResult(file)?.valueOrNull()?.legacyDependencies
}
fun getScriptConfigurationResult(file: VirtualFile): ScriptCompilationConfigurationResult? = null open fun getScriptConfigurationResult(file: KtFile): ScriptCompilationConfigurationResult? = null
fun getScriptConfigurationResult(file: PsiFile): ScriptCompilationConfigurationResult? =
getScriptConfigurationResult(file.virtualFile ?: file.originalFile.virtualFile)
companion object { companion object {
fun getInstance(project: Project): ScriptDependenciesProvider? = fun getInstance(project: Project): ScriptDependenciesProvider? =
@@ -25,18 +25,20 @@ inline fun <T> runReadAction(crossinline runnable: () -> T): T {
fun PsiFile.findScriptDefinition(): ScriptDefinition? { fun PsiFile.findScriptDefinition(): ScriptDefinition? {
// Do not use psiFile.script, see comments in findScriptDefinition // Do not use psiFile.script, see comments in findScriptDefinition
if (this !is KtFile/* || this.script == null*/) return null if (this !is KtFile/* || this.script == null*/) return null
val file = virtualFile ?: originalFile.virtualFile ?: return null
if (file.isNonScript()) return null
return findScriptDefinitionByFilePath(project, File(file.path)) val virtualFile = this.virtualFile ?: this.originalFile.virtualFile ?: return null
if (virtualFile.isNonScript()) return null
return findScriptDefinitionByFilePath(project, File(virtualFile.path))
} }
@Deprecated("Use PsiFile.findScriptDefinition() instead")
fun VirtualFile.findScriptDefinition(project: Project): ScriptDefinition? { fun VirtualFile.findScriptDefinition(project: Project): ScriptDefinition? {
if (!isValid || isNonScript()) return null if (!isValid || isNonScript()) return null
// Do not use psiFile.script here because this method can be called during indexes access // Do not use psiFile.script here because this method can be called during indexes access
// and accessing stubs may cause deadlock // and accessing stubs may cause deadlock
// TODO: measure performance effect and if necessary consider detecting indexing here or using separate logic for non-IDE operations to speed up filtering // TODO: measure performance effect and if necessary consider detecting indexing here or using separate logic for non-IDE operations to speed up filtering
if (runReadAction { PsiManager.getInstance(project).findFile(this) as? KtFile }/*?.script*/ == null) return null if (runReadAction { PsiManager.getInstance(project).findFile(this) as? KtFile }/*?.script*/ == null) return null
return findScriptDefinitionByFilePath(project, File(path)) return findScriptDefinitionByFilePath(project, File(path))
@@ -5,8 +5,6 @@
package org.jetbrains.kotlin.scripting.definitions package org.jetbrains.kotlin.scripting.definitions
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiFile import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult
@@ -27,16 +25,6 @@ fun PsiFile.findScriptCompilationConfiguration(): ScriptCompilationConfiguration
?: findScriptDefinition()?.compilationConfiguration ?: findScriptDefinition()?.compilationConfiguration
} }
fun VirtualFile.findScriptCompilationConfiguration(project: Project): ScriptCompilationConfiguration? {
if (!isValid || isNonScript()) return null
// see comments in findScriptDefinition
// we do not need expensive check as in findScriptDefinition here, since it is assumed that this function is called only for known scripts
val provider = ScriptDependenciesProvider.getInstance(project)
return provider?.getScriptConfigurationResult(this)?.valueOrError()?.configuration
?: findScriptDefinition(project)?.compilationConfiguration
}
private fun ScriptCompilationConfigurationResult.valueOrError() = valueOr { failure -> private fun ScriptCompilationConfigurationResult.valueOrError() = valueOr { failure ->
val singleCause = failure.reports.singleOrNull { it.severity == ScriptDiagnostic.Severity.ERROR } val singleCause = failure.reports.singleOrNull { it.severity == ScriptDiagnostic.Severity.ERROR }
if (singleCause != null) if (singleCause != null)
@@ -29,7 +29,9 @@ class ScriptExtraImportsProviderExtension : ExtraImportsProviderExtension {
override fun getExtraImports(ktFile: KtFile): Collection<KtImportInfo> = override fun getExtraImports(ktFile: KtFile): Collection<KtImportInfo> =
ktFile.takeIf { it.isScript() }?.let { file -> ktFile.takeIf { it.isScript() }?.let { file ->
val refinedConfiguration = ScriptDependenciesProvider.getInstance(file.project)?.getScriptConfigurationResult(file.originalFile)?.valueOrNull() val refinedConfiguration = ScriptDependenciesProvider.getInstance(file.project)
?.getScriptConfigurationResult(file.originalFile as KtFile)
?.valueOrNull()
refinedConfiguration?.defaultImports?.map { refinedConfiguration?.defaultImports?.map {
ScriptExtraImportImpl( ScriptExtraImportImpl(
ImportPath.fromString(it) ImportPath.fromString(it)
@@ -8,38 +8,37 @@ package org.jetbrains.kotlin.scripting.compiler.plugin.definitions
import com.intellij.openapi.components.ServiceManager import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.definitions.ScriptDependenciesProvider import org.jetbrains.kotlin.scripting.definitions.ScriptDependenciesProvider
import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition
import org.jetbrains.kotlin.scripting.resolve.KtFileScriptSource
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult
import org.jetbrains.kotlin.scripting.resolve.ScriptReportSink import org.jetbrains.kotlin.scripting.resolve.ScriptReportSink
import org.jetbrains.kotlin.scripting.resolve.VirtualFileScriptSource
import org.jetbrains.kotlin.scripting.resolve.refineScriptCompilationConfiguration import org.jetbrains.kotlin.scripting.resolve.refineScriptCompilationConfiguration
import java.io.File import java.io.File
import java.util.concurrent.locks.ReentrantReadWriteLock import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read import kotlin.concurrent.read
import kotlin.concurrent.write import kotlin.concurrent.write
import kotlin.script.experimental.api.ResultWithDiagnostics import kotlin.script.experimental.api.ResultWithDiagnostics
import kotlin.script.experimental.jvm.compat.mapToLegacyReports
class CliScriptDependenciesProvider(private val project: Project) : ScriptDependenciesProvider { class CliScriptDependenciesProvider(project: Project) : ScriptDependenciesProvider(project) {
private val cacheLock = ReentrantReadWriteLock() private val cacheLock = ReentrantReadWriteLock()
private val cache = hashMapOf<String, ScriptCompilationConfigurationResult?>() private val cache = hashMapOf<String, ScriptCompilationConfigurationResult?>()
override fun getScriptConfigurationResult(file: VirtualFile): ScriptCompilationConfigurationResult? = cacheLock.read { override fun getScriptConfigurationResult(file: KtFile): ScriptCompilationConfigurationResult? = cacheLock.read {
calculateRefinedConfiguration(file) calculateRefinedConfiguration(file)
} }
private fun calculateRefinedConfiguration(file: VirtualFile): ScriptCompilationConfigurationResult? { private fun calculateRefinedConfiguration(file: KtFile): ScriptCompilationConfigurationResult? {
val path = file.path val path = file.virtualFilePath
val cached = cache[path] val cached = cache[path]
return if (cached != null) cached return if (cached != null) cached
else { else {
val scriptDef = file.findScriptDefinition(project) val scriptDef = file.findScriptDefinition()
if (scriptDef != null) { if (scriptDef != null) {
val result = refineScriptCompilationConfiguration(VirtualFileScriptSource(file), scriptDef, project) val result = refineScriptCompilationConfiguration(KtFileScriptSource(file), scriptDef, project)
ServiceManager.getService(project, ScriptReportSink::class.java)?.attachReports(file, result.reports) ServiceManager.getService(project, ScriptReportSink::class.java)?.attachReports(file.virtualFile, result.reports)
if (result is ResultWithDiagnostics.Success) { if (result is ResultWithDiagnostics.Success) {
log.info("[kts] new cached deps for $path: ${result.value.dependenciesClassPath.joinToString(File.pathSeparator)}") log.info("[kts] new cached deps for $path: ${result.value.dependenciesClassPath.joinToString(File.pathSeparator)}")