From b8034567ef3d450ec91de03df7d1771507258caf Mon Sep 17 00:00:00 2001 From: Ilya Chernikov Date: Tue, 19 Nov 2019 14:02:04 +0100 Subject: [PATCH] Change script definition lookup key from File to ScriptSource to make it more generic and allow easier implementation fo the non-file based scripts. --- .../core/script/ScriptDefinitionsManager.kt | 29 +++++++++++++------ .../MultipleScriptDefinitionsChecker.kt | 4 +-- .../KotlinScriptDefinitionProvider.kt | 6 ++-- .../LazyScriptDefinitionProvider.kt | 22 ++++++++------ .../scripting/definitions/ScriptDefinition.kt | 15 +++++----- .../scripting/definitions/definitions.kt | 12 ++++---- .../AbstractScriptEvaluationExtension.kt | 9 +++--- .../compiler/plugin/ScriptProviderTest.kt | 22 +++++++------- 8 files changed, 70 insertions(+), 49 deletions(-) diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptDefinitionsManager.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptDefinitionsManager.kt index bba86813982..a182a24952e 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptDefinitionsManager.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/ScriptDefinitionsManager.kt @@ -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() private val scriptDefinitionsCacheLock = ReentrantLock() - private val scriptDefinitionsCache = SLRUMap(10, 10) + private val scriptDefinitionsCache = SLRUMap(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 diff --git a/idea/src/org/jetbrains/kotlin/idea/script/configuration/MultipleScriptDefinitionsChecker.kt b/idea/src/org/jetbrains/kotlin/idea/script/configuration/MultipleScriptDefinitionsChecker.kt index 26ec4563f29..4ed3305143e 100644 --- a/idea/src/org/jetbrains/kotlin/idea/script/configuration/MultipleScriptDefinitionsChecker.kt +++ b/idea/src/org/jetbrains/kotlin/idea/script/configuration/MultipleScriptDefinitionsChecker.kt @@ -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() { @@ -42,7 +42,7 @@ class MultipleScriptDefinitionsChecker(private val project: Project) : EditorNot val allApplicableDefinitions = ScriptDefinitionsManager.getInstance(project) .getAllDefinitions() .filter { - it.asLegacyOrNull() == null && it.isScript(File(file.path)) && + it.asLegacyOrNull() == null && it.isScript(KtFileScriptSource(ktFile)) && KotlinScriptingSettings.getInstance(project).isScriptDefinitionEnabled(it) } .toList() diff --git a/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/definitions/KotlinScriptDefinitionProvider.kt b/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/definitions/KotlinScriptDefinitionProvider.kt index 22464b19a2e..901ac570f96 100644 --- a/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/definitions/KotlinScriptDefinitionProvider.kt +++ b/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/definitions/KotlinScriptDefinitionProvider.kt @@ -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 diff --git a/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/definitions/LazyScriptDefinitionProvider.kt b/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/definitions/LazyScriptDefinitionProvider.kt index 6f4d531b9bb..f448d4ce730 100644 --- a/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/definitions/LazyScriptDefinitionProvider.kt +++ b/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/definitions/LazyScriptDefinitionProvider.kt @@ -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 = HashMap() + private var _cachedDefinitions: Sequence? = null private val cachedDefinitions: Sequence 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 = lock.read { cachedDefinitions.map { it.fileExtension } diff --git a/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/definitions/ScriptDefinition.kt b/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/definitions/ScriptDefinition.kt index 3d719b921f8..d858d56a02a 100644 --- a/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/definitions/ScriptDefinition.kt +++ b/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/definitions/ScriptDefinition.kt @@ -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]!! diff --git a/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/definitions/definitions.kt b/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/definitions/definitions.kt index 2c2b1898e41..42c7c39ca9a 100644 --- a/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/definitions/definitions.kt +++ b/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/definitions/definitions.kt @@ -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 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 = diff --git a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/AbstractScriptEvaluationExtension.kt b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/AbstractScriptEvaluationExtension.kt index 00eb1bef8bc..c7361ea3e29 100644 --- a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/AbstractScriptEvaluationExtension.kt +++ b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/AbstractScriptEvaluationExtension.kt @@ -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) { } } } + diff --git a/plugins/scripting/scripting-compiler/tests/org/jetbrains/kotlin/scripting/compiler/plugin/ScriptProviderTest.kt b/plugins/scripting/scripting-compiler/tests/org/jetbrains/kotlin/scripting/compiler/plugin/ScriptProviderTest.kt index e9e9ea2bc75..0645f1d760e 100644 --- a/plugins/scripting/scripting-compiler/tests/org/jetbrains/kotlin/scripting/compiler/plugin/ScriptProviderTest.kt +++ b/plugins/scripting/scripting-compiler/tests/org/jetbrains/kotlin/scripting/compiler/plugin/ScriptProviderTest.kt @@ -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