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) {
override val sdk: Sdk?
get() {
return ScriptDependenciesManager.getInstance(project).getScriptSdk(scriptFile, project)
return ScriptDependenciesManager.getInstance(project).getScriptSdk(scriptFile)
}
override fun contentScope(): GlobalSearchScope {
@@ -71,7 +71,7 @@ object KotlinHighlightingUtil {
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 (!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
}
@@ -53,7 +53,7 @@ class ScriptExternalHighlightingPass(
if (!file.isScript()) return
val reports = IdeScriptReportSink.getReports(file.virtualFile)
val reports = IdeScriptReportSink.getReports(file)
val annotations = reports.mapNotNull { scriptDiagnostic ->
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.psi.PsiElement
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.IdIndexEntry
import com.intellij.psi.search.GlobalSearchScope
@@ -123,7 +124,7 @@ fun PsiSearchHelper.isCheapEnoughToSearchConsideringOperators(
return isCheapEnoughToSearch(name, scope, fileToIgnoreOccurrencesIn, progress)
}
fun findScriptsWithUsages(declaration: KtNamedDeclaration): List<VirtualFile> {
fun findScriptsWithUsages(declaration: KtNamedDeclaration): List<KtFile> {
val project = declaration.project
val scope = PsiSearchHelper.getInstance(project).getUseScope(declaration) as? GlobalSearchScope
?: return emptyList()
@@ -138,5 +139,8 @@ fun findScriptsWithUsages(declaration: KtNamedDeclaration): List<VirtualFile> {
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.psi.PsiManager
import com.intellij.ui.EditorNotifications
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.UserDataProperty
import org.jetbrains.kotlin.scripting.resolve.ScriptReportSink
import kotlin.script.experimental.api.ScriptDiagnostic
@@ -49,6 +50,10 @@ class IdeScriptReportSink(val project: Project) : ScriptReportSink {
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"))
}
}
@@ -18,10 +18,18 @@ package org.jetbrains.kotlin.idea.core.script
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.idea.core.util.*
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.definitions.getScriptOriginalFile
import java.io.*
import kotlin.script.experimental.api.ScriptCompilationConfiguration
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(
name = "kotlin-script-dependencies",
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(
name = "kotlin-script-compilation-configuration",
version = 1,
@@ -23,16 +23,17 @@ import com.intellij.openapi.projectRoots.JavaSdkType
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.roots.ProjectRootManager
import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiManager
import com.intellij.util.io.URLUtil
import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.idea.caches.project.getAllProjectSdks
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.findScriptDefinition
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
import java.io.File
import kotlin.script.experimental.api.valueOrNull
@@ -40,28 +41,35 @@ import kotlin.script.experimental.api.valueOrNull
// NOTE: this service exists exclusively because ScriptDependencyManager
// cannot be registered as implementing two services (state would be duplicated)
class IdeScriptDependenciesProvider(
private val scriptDependenciesManager: ScriptDependenciesManager
) : ScriptDependenciesProvider {
override fun getScriptConfigurationResult(file: VirtualFile): ScriptCompilationConfigurationResult? = scriptDependenciesManager.getRefinedCompilationConfiguration(file)
private val scriptDependenciesManager: ScriptDependenciesManager,
project: Project
) : 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
class ScriptDependenciesManager internal constructor(
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? =
cacheUpdater.getCurrentCompilationConfiguration(file)
fun getScriptSdk(file: VirtualFile, project: Project): Sdk? {
return getScriptSdk(getRefinedCompilationConfiguration(file)?.valueOrNull())
?: getScriptDefaultSdk(project)
@Deprecated("Use getScriptClasspath(KtFile) instead")
fun getScriptClasspath(file: VirtualFile): List<VirtualFile> {
val ktFile = PsiManager.getInstance(project).findFile(file) as? KtFile ?: return emptyList()
return getScriptClasspath(ktFile)
}
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 getScriptSdk(file: VirtualFile) = cache.getScriptSdk(file)
fun getAllScriptsSdks() = cache.allSdks
@@ -76,17 +84,6 @@ class ScriptDependenciesManager internal constructor(
fun getInstance(project: Project): ScriptDependenciesManager =
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? {
val projectSdk = ProjectRootManager.getInstance(project).projectSdk?.takeIf { it.canBeUsedForScript() }
if (projectSdk != null) return projectSdk
@@ -124,10 +121,13 @@ class ScriptDependenciesManager internal constructor(
internal val log = Logger.getInstance(ScriptDependenciesManager::class.java)
@TestOnly
fun updateScriptDependenciesSynchronously(virtualFile: VirtualFile, project: Project) {
fun updateScriptDependenciesSynchronously(file: PsiFile, project: Project) {
val loader = SyncScriptDependenciesLoader(project)
val scriptDefinition = virtualFile.findScriptDefinition(project) ?: return
loader.loadDependencies(virtualFile, scriptDefinition)
val scriptDefinition = file.findScriptDefinition() ?: return
assert(file is KtFile) {
"PsiFile should be a KtFile, otherwise script dependencies cannot be loaded"
}
loader.loadDependencies(file as KtFile, scriptDefinition)
loader.notifyRootsChanged()
}
}
@@ -28,9 +28,9 @@ fun VirtualFile.removeScriptDependenciesNotificationPanel(project: Project) {
}
fun VirtualFile.addScriptDependenciesNotificationPanel(
compilationConfigurationResult: ScriptCompilationConfigurationResult?,
compilationConfigurationResult: ScriptCompilationConfigurationResult,
project: Project,
onClick: (ScriptCompilationConfigurationResult?) -> Unit
onClick: (ScriptCompilationConfigurationResult) -> Unit
) {
withSelectedEditor(project) { manager ->
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 class NewScriptDependenciesNotificationPanel(
onClick: (ScriptCompilationConfigurationResult?) -> Unit,
val compilationConfigurationResult: ScriptCompilationConfigurationResult?,
onClick: (ScriptCompilationConfigurationResult) -> Unit,
val compilationConfigurationResult: ScriptCompilationConfigurationResult,
project: Project
) : EditorNotificationPanel() {
@@ -15,15 +15,19 @@ import com.intellij.openapi.roots.ModuleRootEvent
import com.intellij.openapi.roots.ModuleRootListener
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.roots.OrderRootType
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiElementFinder
import com.intellij.psi.PsiManager
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.search.NonClasspathDirectoriesScope
import com.intellij.util.containers.ConcurrentFactoryMap
import com.intellij.util.containers.SLRUMap
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.jetbrains.kotlin.idea.caches.project.getAllProjectSdks
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.ScriptCompilationConfigurationWrapper
import java.util.concurrent.locks.ReentrantReadWriteLock
@@ -51,45 +55,79 @@ class ScriptsCompilationConfigurationCache(private val project: Project) {
private val cacheLock = ReentrantReadWriteLock()
private val scriptDependenciesCache = SLRUCacheWithLock<ScriptCompilationConfigurationResult>()
private val scriptsModificationStampsCache = SLRUCacheWithLock<Long>()
private val scriptDependenciesCache = SLRUCacheWithLock<VirtualFile, ScriptCompilationConfigurationResult>()
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
}
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 sdk = ScriptDependenciesManager.getScriptSdk(compilationConfiguration)
val sdk = getScriptSdk(file)
@Suppress("FoldInitializerAndIfToElvis")
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() +
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) {
scriptDependenciesCache.getAll()
.mapNotNull { ScriptDependenciesManager.getInstance(project).getScriptSdk(it.key, project) }
.mapNotNull { getScriptSdk(it.key) }
.distinct()
}
private val allNonIndexedSdks by ClearableLazyValue(cacheLock) {
scriptDependenciesCache.getAll()
.mapNotNull { ScriptDependenciesManager.getInstance(project).getScriptSdk(it.key, project) }
.mapNotNull { getScriptSdk(it.key) }
.filterNonModuleSdk()
.distinct()
}
@@ -164,7 +202,7 @@ class ScriptsCompilationConfigurationCache(private val project: Project) {
}
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)) ||
!allDependenciesClassFiles.containsAll(ScriptDependenciesManager.toVfsRoots(compilationConfiguration.dependenciesClassPath)) ||
!allDependenciesSources.containsAll(ScriptDependenciesManager.toVfsRoots(compilationConfiguration.dependenciesSources))
@@ -176,23 +214,23 @@ class ScriptsCompilationConfigurationCache(private val project: Project) {
scriptDependenciesCache.clear()
scriptsModificationStampsCache.clear()
onChange(keys)
updateHighlighting(keys)
}
fun save(virtualFile: VirtualFile, new: ScriptCompilationConfigurationResult): Boolean {
val old = scriptDependenciesCache.replace(virtualFile, new)
fun save(file: VirtualFile, new: ScriptCompilationConfigurationResult): Boolean {
val old = scriptDependenciesCache.replace(file, new)
val changed = new != old
if (changed) {
onChange(listOf(virtualFile))
onChange(listOf(file))
}
return changed
}
fun delete(virtualFile: VirtualFile): Boolean {
val changed = scriptDependenciesCache.remove(virtualFile)
fun delete(file: VirtualFile): Boolean {
val changed = scriptDependenciesCache.remove(file)
if (changed) {
onChange(listOf(virtualFile))
onChange(listOf(file))
}
return changed
}
@@ -226,20 +264,19 @@ private class ClearableLazyValue<in R, out T : Any>(
}
}
private class SLRUCacheWithLock<T> {
private class SLRUCacheWithLock<K, V> {
private val lock = ReentrantReadWriteLock()
val cache = SLRUMap<VirtualFile, T>(
val cache = SLRUMap<K, V>(
ScriptsCompilationConfigurationCache.MAX_SCRIPTS_CACHED,
ScriptsCompilationConfigurationCache.MAX_SCRIPTS_CACHED
)
fun get(value: VirtualFile): T? = lock.write {
fun get(value: K): V? = lock.write {
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)
return if (value == null) {
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)
}
fun getAll(): Collection<Map.Entry<VirtualFile, T>> = lock.write {
fun getAll(): Collection<Map.Entry<K, V>> = lock.write {
cache.entrySet()
}
@@ -262,10 +299,9 @@ private class SLRUCacheWithLock<T> {
cache.clear()
}
fun replace(file: VirtualFile, value: T): T? = lock.write {
fun replace(file: K, value: V): V? = lock.write {
val old = get(file)
cache.put(file, value)
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.psi.KtFile
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.isNonScript
import org.jetbrains.kotlin.scripting.resolve.LegacyResolverWrapper
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult
import kotlin.script.experimental.dependencies.AsyncDependenciesResolver
class ScriptsCompilationConfigurationUpdater(
private val project: Project,
@@ -65,8 +61,10 @@ class ScriptsCompilationConfigurationUpdater(
listenForChangesInScripts()
}
fun getCurrentCompilationConfiguration(file: VirtualFile): ScriptCompilationConfigurationResult? {
cache[file]?.let { return it }
fun getCurrentCompilationConfiguration(file: KtFile): ScriptCompilationConfigurationResult? {
val virtualFile = file.originalFile.virtualFile
cache[virtualFile]?.let { return it }
// Try-catch block is a temporary workaround for ProcessCanceledException
// that may happen during VirtualFile to PsiFile transformation
@@ -76,10 +74,10 @@ class ScriptsCompilationConfigurationUpdater(
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
var wasDependenciesUpdateStarted = false
@@ -97,11 +95,11 @@ class ScriptsCompilationConfigurationUpdater(
return wasDependenciesUpdateStarted
}
private fun updateDependencies(file: VirtualFile) {
private fun updateDependencies(file: KtFile) {
if (!ScriptDefinitionsManager.getInstance(project).isReady()) 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) }
}
@@ -122,24 +120,18 @@ class ScriptsCompilationConfigurationUpdater(
}
private fun runScriptDependenciesUpdateIfNeeded(file: VirtualFile) {
if (!shouldStartUpdate(file)) return
val ktFile = getKtFileToStartConfigurationUpdate(file) ?: return
updateDependencies(file)
updateDependencies(ktFile)
makeRootsChangeIfNeeded()
}
})
EditorFactory.getInstance().eventMulticaster.addDocumentListener(object : DocumentListener {
override fun documentChanged(event: DocumentEvent) {
val document = event.document
val file = FileDocumentManager.getInstance().getFile(document)?.takeIf { it.isInLocalFileSystem } ?: return
if (!file.isValid) {
cache.delete(file)
return
}
if (!shouldStartUpdate(file)) return
val ktFile = getKtFileToStartConfigurationUpdate(file) ?: return
// only update dependencies for scripts that were touched recently
if (cache[file] == null) {
@@ -151,7 +143,7 @@ class ScriptsCompilationConfigurationUpdater(
scriptsQueue.addRequest(
{
FileDocumentManager.getInstance().saveDocument(document)
updateDependencies(file)
updateDependencies(ktFile)
makeRootsChangeIfNeeded()
},
scriptChangesListenerDelay,
@@ -161,31 +153,30 @@ class ScriptsCompilationConfigurationUpdater(
}, project.messageBus.connect())
}
private fun shouldStartUpdate(file: VirtualFile): Boolean {
private fun getKtFileToStartConfigurationUpdate(file: VirtualFile): KtFile? {
if (project.isDisposed || !file.isValid || file.isNonScript()) {
return false
return null
}
if (
ApplicationManager.getApplication().isUnitTestMode &&
ApplicationManager.getApplication().isScriptDependenciesUpdaterDisabled == true
) {
return false
return null
}
val ktFile = PsiManager.getInstance(project).findFile(file) as? KtFile ?: return false
return ProjectRootsUtil.isInProjectSource(ktFile, includeScriptsOutsideSourceRoots = true)
val ktFile = PsiManager.getInstance(project).findFile(file) as? KtFile ?: return null
if (ProjectRootsUtil.isInProjectSource(ktFile, includeScriptsOutsideSourceRoots = true)) {
return ktFile
}
return null
}
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 {
@JvmStatic
fun getInstance(project: Project): ScriptsCompilationConfigurationUpdater =
@@ -194,11 +185,6 @@ class ScriptsCompilationConfigurationUpdater(
fun areDependenciesCached(file: KtFile): Boolean {
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.
*/
@@ -10,13 +10,12 @@ import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.Task
import com.intellij.openapi.progress.util.BackgroundTaskUtil
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
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.psi.KtFile
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
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 java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.locks.ReentrantReadWriteLock
@@ -30,14 +29,14 @@ class AsyncScriptDependenciesLoader internal constructor(project: Project) : Scr
private var backgroundTasksQueue: LoaderBackgroundTask? = null
override fun isApplicable(
file: VirtualFile,
file: KtFile,
scriptDefinition: ScriptDefinition
): Boolean {
return ScriptsCompilationConfigurationUpdater.getInstance(project).isAsyncDependencyResolver(scriptDefinition)
return isAsyncDependencyResolver(scriptDefinition)
}
override fun loadDependencies(
file: VirtualFile,
file: KtFile,
scriptDefinition: ScriptDefinition
) {
lock.write {
@@ -74,20 +73,20 @@ class AsyncScriptDependenciesLoader internal constructor(project: Project) : Scr
return false
}
private fun runDependenciesUpdate(file: VirtualFile) {
val scriptDef = file.findScriptDefinition(project) ?: return
private fun runDependenciesUpdate(file: KtFile) {
val scriptDef = file.findScriptDefinition() ?: return
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" }
processRefinedConfiguration(result, file)
processRefinedConfiguration(result, file.originalFile.virtualFile)
}
private inner class LoaderBackgroundTask {
private val sequenceOfFiles: ConcurrentLinkedQueue<VirtualFile> = ConcurrentLinkedQueue()
private val sequenceOfFiles: ConcurrentLinkedQueue<KtFile> = ConcurrentLinkedQueue()
private var forceStop : Boolean = false
private var startedSilently : Boolean = false
@@ -124,7 +123,7 @@ class AsyncScriptDependenciesLoader internal constructor(project: Project) : Scr
}.queue()
}
fun addTask(file: VirtualFile) {
fun addTask(file: KtFile) {
if (sequenceOfFiles.contains(file)) return
debug(file) { "added to update queue" }
@@ -9,39 +9,41 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.idea.core.script.scriptCompilationConfiguration
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.resolve.KtFileScriptSource
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
import org.jetbrains.kotlin.scripting.resolve.VirtualFileScriptSource
import kotlin.script.experimental.api.asSuccess
// TODO: rename and provide alias for compatibility - this is not only about dependencies anymore
class FromFileAttributeScriptDependenciesLoader(project: Project) : ScriptDependenciesLoader(project) {
override fun isApplicable(
file: VirtualFile,
file: KtFile,
scriptDefinition: ScriptDefinition
): Boolean {
return file.scriptDependencies != null || file.scriptCompilationConfiguration != null
}
override fun loadDependencies(
file: VirtualFile,
file: KtFile,
scriptDefinition: ScriptDefinition
) {
file.scriptCompilationConfiguration?.let {
ScriptCompilationConfigurationWrapper.FromCompilationConfiguration(VirtualFileScriptSource(file), it).apply {
debug(file) { "refined configuration from fileAttributes = $it" }
val virtualFile = file.originalFile.virtualFile
virtualFile.scriptCompilationConfiguration?.let {
ScriptCompilationConfigurationWrapper.FromCompilationConfiguration(KtFileScriptSource(file), it).apply {
debug(virtualFile) { "refined configuration from fileAttributes = $it" }
}
} ?: file.scriptDependencies?.let {
ScriptCompilationConfigurationWrapper.FromLegacy(VirtualFileScriptSource(file), it, scriptDefinition).apply {
debug(file) { "dependencies from fileAttributes = $it" }
} ?: virtualFile.scriptDependencies?.let {
ScriptCompilationConfigurationWrapper.FromLegacy(KtFileScriptSource(file), it, scriptDefinition).apply {
debug(virtualFile) { "dependencies from fileAttributes = $it" }
}
}?.let {
if (areDependenciesValid(file, it)) {
saveToCache(file, it.asSuccess(), skipSaveToAttributes = true)
if (areDependenciesValid(virtualFile, it)) {
saveToCache(virtualFile, it.asSuccess(), skipSaveToAttributes = true)
} else {
file.scriptCompilationConfiguration = null
file.scriptDependencies = null
virtualFile.scriptCompilationConfiguration = 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.vfs.VirtualFile
import com.intellij.psi.PsiManager
import com.intellij.psi.ResolveScopeProvider
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.StandardIdeScriptDefinition
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition
import org.jetbrains.kotlin.scripting.resolve.KotlinScriptDefinitionFromAnnotatedTemplate
@@ -33,7 +36,10 @@ class KotlinScriptResolveScopeProvider : ResolveScopeProvider() {
}
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 {
scriptDefinition == null -> null
// 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
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.highlighter.OutsidersPsiFileSupportUtils
import org.jetbrains.kotlin.idea.highlighter.OutsidersPsiFileSupportWrapper
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
class OutsiderFileDependenciesLoader(project: Project) : ScriptDependenciesLoader(project) {
override fun isApplicable(
file: VirtualFile,
file: KtFile,
scriptDefinition: ScriptDefinition
): Boolean {
return OutsidersPsiFileSupportWrapper.isOutsiderFile(file)
val virtualFile = file.virtualFile ?: return false
return OutsidersPsiFileSupportWrapper.isOutsiderFile(virtualFile)
}
override fun loadDependencies(
file: VirtualFile,
file: KtFile,
scriptDefinition: ScriptDefinition
) {
val fileOrigin = OutsidersPsiFileSupportUtils.getOutsiderFileOrigin(project, file) ?: return
val compilationConfiguration = ScriptDependenciesManager.getInstance(project).getRefinedCompilationConfiguration(fileOrigin) ?: return
saveToCache(file, compilationConfiguration)
val virtualFile = file.virtualFile ?: return
val fileOrigin = OutsidersPsiFileSupportUtils.getOutsiderFileOrigin(project, virtualFile) ?: return
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
@@ -15,18 +15,22 @@ import com.intellij.openapi.util.EmptyRunnable
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.idea.core.script.*
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.resolve.LegacyResolverWrapper
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
import org.jetbrains.kotlin.scripting.resolve.ScriptReportSink
import kotlin.script.experimental.api.ResultWithDiagnostics
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
abstract class ScriptDependenciesLoader(protected val project: Project) {
abstract fun isApplicable(file: VirtualFile, scriptDefinition: ScriptDefinition): Boolean
abstract fun loadDependencies(file: VirtualFile, scriptDefinition: ScriptDefinition)
abstract fun isApplicable(file: KtFile, scriptDefinition: ScriptDefinition): Boolean
abstract fun loadDependencies(file: KtFile, scriptDefinition: ScriptDefinition)
protected abstract fun shouldShowNotification(): Boolean
@@ -138,13 +142,23 @@ abstract class ScriptDependenciesLoader(protected val project: Project) {
return true
}
companion object {
private val LOG = Logger.getInstance("#org.jetbrains.kotlin.idea.script")
protected fun isAsyncDependencyResolver(scriptDef: ScriptDefinition): Boolean =
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
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.idea.core.script.ScriptsCompilationConfigurationUpdater
import org.jetbrains.kotlin.psi.KtFile
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
class SyncScriptDependenciesLoader(project: Project) : ScriptDependenciesLoader(project) {
override fun isApplicable(
file: VirtualFile,
file: KtFile,
scriptDefinition: ScriptDefinition
): Boolean {
return !ScriptsCompilationConfigurationUpdater.getInstance(project).isAsyncDependencyResolver(scriptDefinition)
return !isAsyncDependencyResolver(scriptDefinition)
}
override fun loadDependencies(
file: VirtualFile,
file: KtFile,
scriptDefinition: ScriptDefinition
) {
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" }
processRefinedConfiguration(result, file)
processRefinedConfiguration(result, file.originalFile.virtualFile)
}
override fun shouldShowNotification(): Boolean = false
@@ -48,7 +48,7 @@ class GradleBuildFileHighlightingTest : GradleImportingTestCase() {
val psiFile = PsiManager.getInstance(myProject).findFile(file) as? KtFile
?: error("Couldn't find psiFile for virtual file: ${file.canonicalPath}")
ScriptDependenciesManager.updateScriptDependenciesSynchronously(file, myProject)
ScriptDependenciesManager.updateScriptDependenciesSynchronously(psiFile, myProject)
val bindingContext = psiFile.analyzeWithContent()
@@ -166,7 +166,7 @@ class KtScratchExecutionSession(
}
ScriptDependenciesManager.getInstance(originalFile.project)
.getRefinedCompilationConfiguration(originalFile.virtualFile)?.valueOrNull()?.let {
.getRefinedCompilationConfiguration(originalFile)?.valueOrNull()?.let {
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 scriptExtraImports = contextFile.takeIf { it.isScript() }?.let { ktFile ->
val scriptDependencies =
ScriptDependenciesProvider.getInstance(ktFile.project)?.getScriptConfigurationResult(ktFile.originalFile)?.valueOrNull()
val scriptDependencies = ScriptDependenciesProvider.getInstance(ktFile.project)
?.getScriptConfigurationResult(ktFile.originalFile as KtFile)
?.valueOrNull()
scriptDependencies?.defaultImports?.map { ImportPath.fromString(it) }
}.orEmpty()
@@ -12,10 +12,7 @@ import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.psi.PsiComment
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiJavaFile
import com.intellij.psi.*
import com.intellij.psi.codeStyle.JavaCodeStyleManager
import com.intellij.psi.codeStyle.VariableKind
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.LightCodeInsightFixtureTestCase
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.util.CodeInsightUtils
import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively
import org.jetbrains.kotlin.idea.refactoring.chooseMembers
import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.ExtractSuperInfo
@@ -325,7 +322,9 @@ abstract class AbstractExtractionTest : KotlinLightCodeInsightFixtureTestCase()
PluginTestCaseBase.addJdk(myFixture.projectDisposable, PluginTestCaseBase::mockJdk)
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}"
@@ -71,7 +71,7 @@ class StandaloneScriptRunConfigurationTest : KotlinCodeInsightTestCase() {
fun testOnFileMoveWithDefaultWorkingDir() {
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 runConfiguration = createConfigurationFromElement(script, save = true) as KotlinStandaloneScriptRunConfiguration
@@ -98,7 +98,7 @@ class StandaloneScriptRunConfigurationTest : KotlinCodeInsightTestCase() {
fun testOnFileMoveWithNonDefaultWorkingDir() {
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 runConfiguration = createConfigurationFromElement(script, save = true) as KotlinStandaloneScriptRunConfiguration
@@ -204,7 +204,7 @@ abstract class AbstractScratchRunActionTest : FileEditorManagerTestCase() {
myFixture.openFileInEditor(scratchVirtualFile)
ScriptDependenciesManager.updateScriptDependenciesSynchronously(scratchVirtualFile, project)
ScriptDependenciesManager.updateScriptDependenciesSynchronously(myFixture.file, project)
val scratchFileEditor = getScratchEditorForSelectedFile(myManager, myFixture.file.virtualFile)
?: error("Couldn't find scratch file")
@@ -217,7 +217,7 @@ abstract class AbstractScratchRunActionTest : FileEditorManagerTestCase() {
protected fun configureWorksheetByText(name: String, text: String): ScratchFile {
val worksheetFile = myFixture.configureByText(name, text).virtualFile
ScriptDependenciesManager.updateScriptDependenciesSynchronously(worksheetFile, project)
ScriptDependenciesManager.updateScriptDependenciesSynchronously(myFixture.file, project)
val scratchFileEditor = getScratchEditorForSelectedFile(myManager, myFixture.file.virtualFile)
?: error("Couldn't find scratch panel")
@@ -37,7 +37,7 @@ abstract class AbstractScratchLineMarkersTest : FileEditorManagerTestCase() {
myFixture.openFileInEditor(scratchVirtualFile)
ScriptDependenciesManager.updateScriptDependenciesSynchronously(scratchVirtualFile, project)
ScriptDependenciesManager.updateScriptDependenciesSynchronously(myFixture.file, project)
val scratchFileEditor = getScratchEditorForSelectedFile(FileEditorManager.getInstance(project), myFixture.file.virtualFile)
?: 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.test.PluginTestCaseBase
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.KotlinTestUtils
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)
}
}
@@ -134,7 +135,7 @@ abstract class AbstractScriptDefinitionsOrderTest : AbstractScriptConfigurationT
}
ScriptDefinitionsManager.getInstance(project).reorderScriptDefinitions()
updateScriptDependenciesSynchronously(myFile.virtualFile, project)
updateScriptDependenciesSynchronously(myFile, project)
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")
configureByExistingFile(script)
updateScriptDependenciesSynchronously(script, project)
updateScriptDependenciesSynchronously(myFile, project)
VfsUtil.markDirtyAndRefresh(false, true, true, project.baseDir)
// This is needed because updateScriptDependencies invalidates psiFile that was stored in myFile field
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)) {
"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.vfs.VirtualFile
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiManager
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult
import kotlin.script.experimental.api.valueOrNull
import kotlin.script.experimental.dependencies.ScriptDependencies
interface ScriptDependenciesProvider {
open class ScriptDependenciesProvider constructor(
protected val project: Project
) {
@Deprecated("Migrating to configuration refinement", level = DeprecationLevel.ERROR)
fun getScriptDependencies(file: VirtualFile): ScriptDependencies? =
getScriptConfigurationResult(file)?.valueOrNull()?.legacyDependencies
fun getScriptDependencies(file: VirtualFile): ScriptDependencies? {
val ktFile = PsiManager.getInstance(project).findFile(file) as? KtFile ?: return null
return getScriptConfigurationResult(ktFile)?.valueOrNull()?.legacyDependencies
}
@Deprecated("Migrating to configuration refinement", level = DeprecationLevel.ERROR)
fun getScriptDependencies(file: PsiFile): ScriptDependencies? =
getScriptConfigurationResult(file.virtualFile ?: file.originalFile.virtualFile)?.valueOrNull()?.legacyDependencies
fun getScriptDependencies(file: PsiFile): ScriptDependencies? {
if (file !is KtFile) return null
return getScriptConfigurationResult(file)?.valueOrNull()?.legacyDependencies
}
fun getScriptConfigurationResult(file: VirtualFile): ScriptCompilationConfigurationResult? = null
fun getScriptConfigurationResult(file: PsiFile): ScriptCompilationConfigurationResult? =
getScriptConfigurationResult(file.virtualFile ?: file.originalFile.virtualFile)
open fun getScriptConfigurationResult(file: KtFile): ScriptCompilationConfigurationResult? = null
companion object {
fun getInstance(project: Project): ScriptDependenciesProvider? =
@@ -25,18 +25,20 @@ inline fun <T> runReadAction(crossinline runnable: () -> T): T {
fun PsiFile.findScriptDefinition(): ScriptDefinition? {
// Do not use psiFile.script, see comments in findScriptDefinition
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? {
if (!isValid || isNonScript()) return null
// Do not use psiFile.script here because this method can be called during indexes access
// 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
if (runReadAction { PsiManager.getInstance(project).findFile(this) as? KtFile }/*?.script*/ == null) return null
return findScriptDefinitionByFilePath(project, File(path))
@@ -5,8 +5,6 @@
package org.jetbrains.kotlin.scripting.definitions
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult
@@ -27,16 +25,6 @@ fun PsiFile.findScriptCompilationConfiguration(): ScriptCompilationConfiguration
?: 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 ->
val singleCause = failure.reports.singleOrNull { it.severity == ScriptDiagnostic.Severity.ERROR }
if (singleCause != null)
@@ -29,7 +29,9 @@ class ScriptExtraImportsProviderExtension : ExtraImportsProviderExtension {
override fun getExtraImports(ktFile: KtFile): Collection<KtImportInfo> =
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 {
ScriptExtraImportImpl(
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.diagnostic.Logger
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.findScriptDefinition
import org.jetbrains.kotlin.scripting.resolve.KtFileScriptSource
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult
import org.jetbrains.kotlin.scripting.resolve.ScriptReportSink
import org.jetbrains.kotlin.scripting.resolve.VirtualFileScriptSource
import org.jetbrains.kotlin.scripting.resolve.refineScriptCompilationConfiguration
import java.io.File
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.write
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 cache = hashMapOf<String, ScriptCompilationConfigurationResult?>()
override fun getScriptConfigurationResult(file: VirtualFile): ScriptCompilationConfigurationResult? = cacheLock.read {
override fun getScriptConfigurationResult(file: KtFile): ScriptCompilationConfigurationResult? = cacheLock.read {
calculateRefinedConfiguration(file)
}
private fun calculateRefinedConfiguration(file: VirtualFile): ScriptCompilationConfigurationResult? {
val path = file.path
private fun calculateRefinedConfiguration(file: KtFile): ScriptCompilationConfigurationResult? {
val path = file.virtualFilePath
val cached = cache[path]
return if (cached != null) cached
else {
val scriptDef = file.findScriptDefinition(project)
val scriptDef = file.findScriptDefinition()
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) {
log.info("[kts] new cached deps for $path: ${result.value.dependenciesClassPath.joinToString(File.pathSeparator)}")