Change script definition lookup key from File to ScriptSource

to make it more generic and allow easier implementation fo the
non-file based scripts.
This commit is contained in:
Ilya Chernikov
2019-11-19 14:02:04 +01:00
parent 615624802c
commit b8034567ef
8 changed files with 70 additions and 49 deletions
@@ -23,6 +23,7 @@ import com.intellij.openapi.projectRoots.ex.PathUtilEx
import com.intellij.openapi.roots.ProjectRootManager
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.util.containers.SLRUMap
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.caches.project.SdkInfo
@@ -31,6 +32,7 @@ import org.jetbrains.kotlin.idea.core.script.settings.KotlinScriptingSettings
import org.jetbrains.kotlin.idea.util.getProjectJdkTableSafe
import org.jetbrains.kotlin.script.ScriptTemplatesProvider
import org.jetbrains.kotlin.scripting.definitions.*
import org.jetbrains.kotlin.scripting.resolve.VirtualFileScriptSource
import org.jetbrains.kotlin.utils.PathUtil
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import org.jetbrains.kotlin.utils.addToStdlib.flattenTo
@@ -41,11 +43,13 @@ import kotlin.concurrent.withLock
import kotlin.concurrent.write
import kotlin.script.dependencies.Environment
import kotlin.script.dependencies.ScriptContents
import kotlin.script.experimental.api.SourceCode
import kotlin.script.experimental.dependencies.DependenciesResolver
import kotlin.script.experimental.dependencies.ScriptDependencies
import kotlin.script.experimental.dependencies.asSuccess
import kotlin.script.experimental.host.ScriptingHostConfiguration
import kotlin.script.experimental.host.configurationDependencies
import kotlin.script.experimental.host.toScriptSource
import kotlin.script.experimental.jvm.JvmDependency
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
import kotlin.script.experimental.jvm.util.scriptCompilationClasspathFromContextOrStdlib
@@ -58,32 +62,39 @@ class ScriptDefinitionsManager(private val project: Project) : LazyScriptDefinit
private val failedContributorsHashes = HashSet<Int>()
private val scriptDefinitionsCacheLock = ReentrantLock()
private val scriptDefinitionsCache = SLRUMap<File, ScriptDefinition>(10, 10)
private val scriptDefinitionsCache = SLRUMap<String, ScriptDefinition>(10, 10)
override fun findDefinition(file: File): ScriptDefinition? {
if (nonScriptFileName(file.name)) return null
override fun findDefinition(script: SourceCode): ScriptDefinition? {
val locationId = script.locationId ?: return null
if (nonScriptId(locationId)) return null
if (!isReady()) return null
val cached = scriptDefinitionsCacheLock.withLock { scriptDefinitionsCache.get(file) }
val cached = scriptDefinitionsCacheLock.withLock { scriptDefinitionsCache.get(locationId) }
if (cached != null) return cached
val virtualFile = VfsUtil.findFileByIoFile(file, true)
val definition =
if (virtualFile != null && ScratchFileService.getInstance().getRootType(virtualFile) is ScratchRootType) {
if (isScratchFile(script)) {
// Scratch should always have default script definition
getDefaultDefinition()
} else {
super.findDefinition(file) ?: return null
super.findDefinition(script) ?: return null
}
scriptDefinitionsCacheLock.withLock {
scriptDefinitionsCache.put(file, definition)
scriptDefinitionsCache.put(locationId, definition)
}
return definition
}
override fun findScriptDefinition(fileName: String): KotlinScriptDefinition? = findDefinition(File(fileName))?.legacyDefinition
private fun isScratchFile(script: SourceCode): Boolean {
val virtualFile =
if (script is VirtualFileScriptSource) script.virtualFile
else script.locationId?.let { VirtualFileManager.getInstance().findFileByUrl(it) }
return virtualFile != null && ScratchFileService.getInstance().getRootType(virtualFile) is ScratchRootType
}
override fun findScriptDefinition(fileName: String): KotlinScriptDefinition? = findDefinition(File(fileName).toScriptSource())?.legacyDefinition
fun reloadDefinitionsBy(source: ScriptDefinitionsSource) = lock.write {
if (definitions == null) return // not loaded yet
@@ -25,7 +25,7 @@ import org.jetbrains.kotlin.parsing.KotlinParserDefinition
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
import org.jetbrains.kotlin.scripting.resolve.KotlinScriptDefinitionFromAnnotatedTemplate
import java.io.File
import org.jetbrains.kotlin.scripting.resolve.KtFileScriptSource
class MultipleScriptDefinitionsChecker(private val project: Project) : EditorNotifications.Provider<EditorNotificationPanel>() {
@@ -42,7 +42,7 @@ class MultipleScriptDefinitionsChecker(private val project: Project) : EditorNot
val allApplicableDefinitions = ScriptDefinitionsManager.getInstance(project)
.getAllDefinitions()
.filter {
it.asLegacyOrNull<StandardIdeScriptDefinition>() == null && it.isScript(File(file.path)) &&
it.asLegacyOrNull<StandardIdeScriptDefinition>() == null && it.isScript(KtFileScriptSource(ktFile)) &&
KotlinScriptingSettings.getInstance(project).isScriptDefinitionEnabled(it)
}
.toList()
@@ -7,7 +7,7 @@ package org.jetbrains.kotlin.scripting.definitions
import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.project.Project
import java.io.File
import kotlin.script.experimental.api.SourceCode
interface ScriptDefinitionProvider {
@Deprecated("Migrating to configuration refinement", level = DeprecationLevel.ERROR)
@@ -16,9 +16,9 @@ interface ScriptDefinitionProvider {
@Deprecated("Migrating to configuration refinement", level = DeprecationLevel.ERROR)
fun getDefaultScriptDefinition(): KotlinScriptDefinition
fun isScript(file: File): Boolean
fun isScript(script: SourceCode): Boolean
fun findDefinition(file: File): ScriptDefinition?
fun findDefinition(script: SourceCode): ScriptDefinition?
fun getDefaultDefinition(): ScriptDefinition
fun getKnownFilenameExtensions(): Sequence<String>
@@ -7,10 +7,11 @@ package org.jetbrains.kotlin.scripting.definitions
import com.intellij.ide.highlighter.JavaFileType
import org.jetbrains.kotlin.idea.KotlinFileType
import java.io.File
import java.net.URI
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.write
import kotlin.script.experimental.api.SourceCode
import kotlin.script.experimental.host.ScriptingHostConfiguration
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
@@ -25,6 +26,8 @@ abstract class LazyScriptDefinitionProvider : ScriptDefinitionProvider {
override fun getDefaultDefinition(): ScriptDefinition =
ScriptDefinition.getDefault(getScriptingHostConfiguration())
protected val fixedDefinitions: HashMap<URI, ScriptDefinition> = HashMap()
private var _cachedDefinitions: Sequence<ScriptDefinition>? = null
private val cachedDefinitions: Sequence<ScriptDefinition>
get() {
@@ -41,23 +44,24 @@ abstract class LazyScriptDefinitionProvider : ScriptDefinitionProvider {
}
}
protected open fun nonScriptFileName(fileName: String) = nonScriptFilenameSuffixes.any {
fileName.endsWith(it, ignoreCase = true)
}
protected open fun nonScriptId(locationId: String): Boolean =
nonScriptFilenameSuffixes.any {
locationId.endsWith(it, ignoreCase = true)
}
override fun findDefinition(file: File): ScriptDefinition? =
if (nonScriptFileName(file.name)) null
override fun findDefinition(script: SourceCode): ScriptDefinition? =
if (script.locationId == null || nonScriptId(script.locationId!!)) null
else lock.read {
cachedDefinitions.firstOrNull { it.isScript(file) }
cachedDefinitions.firstOrNull { it.isScript(script) }
}
override fun findScriptDefinition(fileName: String): KotlinScriptDefinition? =
if (nonScriptFileName(fileName)) null
if (nonScriptId(fileName)) null
else lock.read {
cachedDefinitions.map { it.legacyDefinition }.firstOrNull { it.isScript(fileName) }
}
override fun isScript(file: File) = findDefinition(file) != null
override fun isScript(script: SourceCode): Boolean = findDefinition(script) != null
override fun getKnownFilenameExtensions(): Sequence<String> = lock.read {
cachedDefinitions.map { it.fileExtension }
@@ -28,7 +28,7 @@ abstract class ScriptDefinition : UserDataHolderBase() {
abstract val compilationConfiguration: ScriptCompilationConfiguration
abstract val evaluationConfiguration: ScriptEvaluationConfiguration?
abstract fun isScript(file: File): Boolean
abstract fun isScript(script: SourceCode): Boolean
abstract val fileExtension: String
abstract val name: String
open val defaultClassName: String = "Script"
@@ -75,7 +75,7 @@ abstract class ScriptDefinition : UserDataHolderBase() {
)
}
override fun isScript(file: File): Boolean = legacyDefinition.isScript(file.name)
override fun isScript(script: SourceCode): Boolean = script.name?.let { legacyDefinition.isScript(it) } ?: isDefault
override val fileExtension: String get() = legacyDefinition.fileExtension
@@ -130,11 +130,12 @@ abstract class ScriptDefinition : UserDataHolderBase() {
compilationConfiguration[ScriptCompilationConfiguration.filePathPattern]?.takeIf { it.isNotBlank() }
}
override fun isScript(file: File): Boolean =
file.name.endsWith(".$fileExtension") &&
(filePathPattern?.let {
Regex(it).matches(FileUtilRt.toSystemIndependentName(file.path))
} ?: true)
override fun isScript(script: SourceCode): Boolean {
val location = script.locationId ?: return false
return location.endsWith(".$fileExtension") && filePathPattern?.let {
Regex(it).matches(FileUtilRt.toSystemIndependentName(location))
} != false
}
override val fileExtension: String get() = compilationConfiguration[ScriptCompilationConfiguration.fileExtension]!!
@@ -16,7 +16,9 @@ import com.intellij.psi.PsiManager
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.parsing.KotlinParserDefinition
import org.jetbrains.kotlin.psi.KtFile
import java.io.File
import org.jetbrains.kotlin.scripting.resolve.KtFileScriptSource
import org.jetbrains.kotlin.scripting.resolve.VirtualFileScriptSource
import kotlin.script.experimental.api.SourceCode
inline fun <T> runReadAction(crossinline runnable: () -> T): T {
return ApplicationManager.getApplication().runReadAction(Computable { runnable() })
@@ -29,7 +31,7 @@ fun PsiFile.findScriptDefinition(): ScriptDefinition? {
val virtualFile = this.virtualFile ?: this.originalFile.virtualFile ?: return null
if (virtualFile.isNonScript()) return null
return findScriptDefinitionByFilePath(project, File(virtualFile.path))
return findScriptDefinition(project, KtFileScriptSource(this))
}
@Deprecated("Use PsiFile.findScriptDefinition() instead")
@@ -41,14 +43,14 @@ fun VirtualFile.findScriptDefinition(project: Project): ScriptDefinition? {
// 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))
return findScriptDefinition(project, VirtualFileScriptSource(this))
}
private fun findScriptDefinitionByFilePath(project: Project, file: File): ScriptDefinition {
fun findScriptDefinition(project: Project, script: SourceCode): ScriptDefinition? {
val scriptDefinitionProvider = ScriptDefinitionProvider.getInstance(project) ?: return null
?: throw IllegalStateException("Unable to get script definition: ScriptDefinitionProvider is not configured.")
return scriptDefinitionProvider.findDefinition(file) ?: scriptDefinitionProvider.getDefaultDefinition()
return scriptDefinitionProvider.findDefinition(script) ?: scriptDefinitionProvider.getDefaultDefinition()
}
fun VirtualFile.isNonScript(): Boolean =
@@ -55,7 +55,9 @@ abstract class AbstractScriptEvaluationExtension : ScriptEvaluationExtension {
if (messageCollector.hasErrors()) return ExitCode.COMPILATION_ERROR
val scriptFile = File(sourcePath)
if (scriptFile.isDirectory || !scriptDefinitionProvider.isScript(scriptFile)) {
val script = scriptFile.toScriptSource()
if (scriptFile.isDirectory || !scriptDefinitionProvider.isScript(script)) {
val extensionHint =
if (configuration.get(ScriptingConfigurationKeys.SCRIPT_DEFINITIONS)?.let { it.size == 1 && it.first().isDefault } == true) " (.kts)"
else ""
@@ -63,9 +65,7 @@ abstract class AbstractScriptEvaluationExtension : ScriptEvaluationExtension {
return ExitCode.COMPILATION_ERROR
}
val script = scriptFile.toScriptSource()
val definition = scriptDefinitionProvider.findDefinition(scriptFile) ?: scriptDefinitionProvider.getDefaultDefinition()
val definition = scriptDefinitionProvider.findDefinition(script) ?: scriptDefinitionProvider.getDefaultDefinition()
val scriptArgs =
if (arguments.freeArgs.isNotEmpty()) arguments.freeArgs.subList(1, arguments.freeArgs.size)
@@ -134,3 +134,4 @@ private fun ResultValue.Error.renderError(stream: PrintStream) {
}
}
}
@@ -9,12 +9,13 @@ import org.jetbrains.kotlin.cli.common.environment.setIdeaIoUseFallback
import org.jetbrains.kotlin.scripting.compiler.plugin.definitions.CliScriptDefinitionProvider
import org.jetbrains.kotlin.scripting.definitions.KotlinScriptDefinition
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinitionProvider
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinitionsSource
import org.junit.Assert
import org.junit.Test
import java.io.File
import java.util.concurrent.atomic.AtomicInteger
import kotlin.script.experimental.api.SourceCode
import kotlin.script.experimental.host.toScriptSource
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
import kotlin.script.templates.standard.ScriptTemplateWithArgs
@@ -43,43 +44,44 @@ class ScriptProviderTest {
Assert.assertEquals(0, genDefCounter.get())
provider.isScript("a.kt").let {
provider.isScript(File("a.kt").toScriptSource()).let {
Assert.assertFalse(it)
Assert.assertEquals(0, genDefCounter.get())
}
provider.isScript("a.y.kts").let {
provider.isScript(File("a.y.kts").toScriptSource()).let {
Assert.assertTrue(it)
Assert.assertEquals(1, genDefCounter.get())
}
provider.isScript("a.x.kts").let {
provider.isScript(File("a.x.kts").toScriptSource()).let {
Assert.assertTrue(it)
Assert.assertEquals(1, genDefCounter.get())
Assert.assertEquals(1, shadedDef.matchCounter.get())
}
provider.isScript("a.z.kts").let {
provider.isScript(File("a.z.kts").toScriptSource()).let {
Assert.assertTrue(it)
Assert.assertEquals(2, genDefCounter.get())
Assert.assertEquals(1, standardDef.matchCounter.get())
}
provider.isScript("a.ktx").let {
provider.isScript(File("a.ktx").toScriptSource()).let {
Assert.assertFalse(it)
Assert.assertEquals(2, genDefCounter.get())
}
}
private fun ScriptDefinitionProvider.isScript(fileName: String) = isScript(File(fileName))
}
private open class FakeScriptDefinition(val suffix: String = ".kts") :
ScriptDefinition.FromLegacy(defaultJvmScriptingHostConfiguration, KotlinScriptDefinition(ScriptTemplateWithArgs::class))
{
val matchCounter = AtomicInteger()
override fun isScript(file: File): Boolean = file.name.endsWith(suffix).also {
if (it) matchCounter.incrementAndGet()
override fun isScript(script: SourceCode): Boolean {
val path =script.locationId ?: return false
return path.endsWith(suffix).also {
if (it) matchCounter.incrementAndGet()
}
}
override val isDefault: Boolean