Reorganize files and folders in the scripting plugin

This commit is contained in:
Ilya Chernikov
2019-02-15 12:27:38 +01:00
parent adb4d264ec
commit 336f43dbf2
40 changed files with 114 additions and 61 deletions
@@ -0,0 +1,58 @@
description = "Kotlin Scripting Compiler Plugin"
plugins {
kotlin("jvm")
id("jps-compatible")
}
dependencies {
compileOnly(project(":compiler:frontend"))
compileOnly(project(":compiler:frontend.java"))
compileOnly(project(":compiler:psi"))
compileOnly(project(":compiler:plugin-api"))
compileOnly(project(":compiler:cli"))
compile(project(":kotlin-scripting-common"))
compile(project(":kotlin-scripting-jvm"))
compile(kotlinStdlib())
compileOnly(project(":kotlin-reflect-api"))
compileOnly(intellijCoreDep()) { includeJars("intellij-core") }
compileOnly(intellijDep()) { includeJars("asm-all", rootProject = rootProject) }
testCompile(project(":compiler:frontend"))
testCompile(project(":compiler:frontend.script"))
testCompile(project(":compiler:plugin-api"))
testCompile(project(":compiler:util"))
testCompile(project(":compiler:cli"))
testCompile(project(":compiler:cli-common"))
testCompile(project(":compiler:frontend.java"))
testCompile(projectTests(":compiler:tests-common"))
testCompile(commonDep("junit:junit"))
}
sourceSets {
"main" { projectDefault() }
"test" { projectDefault() }
}
tasks.withType<org.jetbrains.kotlin.gradle.dsl.KotlinCompile<*>> {
kotlinOptions {
languageVersion = "1.2"
apiVersion = "1.2"
freeCompilerArgs += "-Xskip-metadata-version-check"
}
}
publish()
val jar = runtimeJar {}
sourcesJar()
javadocJar()
dist()
ideaPlugin()
projectTest {
workingDir = rootDir
}
@@ -0,0 +1 @@
org.jetbrains.kotlin.scripting.compiler.plugin.ScriptingCommandLineProcessor
@@ -0,0 +1 @@
org.jetbrains.kotlin.scripting.compiler.plugin.ScriptingCompilerConfigurationComponentRegistrar
@@ -0,0 +1,134 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.scripting.compiler.plugin
import org.jetbrains.kotlin.compiler.plugin.AbstractCliOption
import java.io.File
import org.jetbrains.kotlin.compiler.plugin.CliOption
import org.jetbrains.kotlin.compiler.plugin.CliOptionProcessingException
import org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.CompilerConfigurationKey
import org.jetbrains.kotlin.config.JVMConfigurationKeys
object ScriptingConfigurationKeys {
val DISABLE_SCRIPTING_PLUGIN_OPTION: CompilerConfigurationKey<Boolean> =
CompilerConfigurationKey.create("Disable scripting plugin")
val SCRIPT_DEFINITIONS: CompilerConfigurationKey<List<String>> =
CompilerConfigurationKey.create("Script definition classes")
val SCRIPT_DEFINITIONS_CLASSPATH: CompilerConfigurationKey<List<File>> =
CompilerConfigurationKey.create("Additional classpath for the script definitions")
val DISABLE_SCRIPT_DEFINITIONS_FROM_CLASSPATH_OPTION: CompilerConfigurationKey<Boolean> =
CompilerConfigurationKey.create("Do not extract script definitions from the compilation classpath")
val LEGACY_SCRIPT_RESOLVER_ENVIRONMENT_OPTION: CompilerConfigurationKey<MutableMap<String, Any?>> =
CompilerConfigurationKey.create("Script resolver environment")
}
class ScriptingCommandLineProcessor : CommandLineProcessor {
companion object {
val DISABLE_SCRIPTING_PLUGIN_OPTION = CliOption(
"disable", "true/false", "Disable scripting plugin",
required = false, allowMultipleOccurrences = false
)
val SCRIPT_DEFINITIONS_OPTION = CliOption(
"script-definitions", "<fully qualified class name[,]>", "Script definition classes",
required = false, allowMultipleOccurrences = true
)
val SCRIPT_DEFINITIONS_CLASSPATH_OPTION = CliOption(
"script-definitions-classpath", "<classpath entry[:]>", "Additional classpath for the script definitions",
required = false, allowMultipleOccurrences = true
)
val DISABLE_STANDARD_SCRIPT_DEFINITION_OPTION = CliOption(
"disable-standard-script", "true/false", "Disable standard kotlin script support",
required = false, allowMultipleOccurrences = false
)
val DISABLE_SCRIPT_DEFINITIONS_FROM_CLSSPATH_OPTION = CliOption(
"disable-script-definitions-from-classpath", "true/false", "Do not extract script definitions from the compilation classpath",
required = false, allowMultipleOccurrences = false
)
val LEGACY_SCRIPT_TEMPLATES_OPTION = CliOption(
"script-templates", "<fully qualified class name[,]>", "Script definition template classes",
required = false, allowMultipleOccurrences = true
)
val LEGACY_SCRIPT_RESOLVER_ENVIRONMENT_OPTION = CliOption(
"script-resolver-environment", "<key=value[,]>",
"Script resolver environment in key-value pairs (the value could be quoted and escaped)",
required = false, allowMultipleOccurrences = true
)
val PLUGIN_ID = "kotlin.scripting"
}
override val pluginId = PLUGIN_ID
override val pluginOptions =
listOf(
DISABLE_SCRIPTING_PLUGIN_OPTION,
SCRIPT_DEFINITIONS_OPTION,
SCRIPT_DEFINITIONS_CLASSPATH_OPTION,
DISABLE_STANDARD_SCRIPT_DEFINITION_OPTION,
DISABLE_SCRIPT_DEFINITIONS_FROM_CLSSPATH_OPTION,
LEGACY_SCRIPT_TEMPLATES_OPTION,
LEGACY_SCRIPT_RESOLVER_ENVIRONMENT_OPTION
)
override fun processOption(option: AbstractCliOption, value: String, configuration: CompilerConfiguration) = when (option) {
DISABLE_SCRIPTING_PLUGIN_OPTION -> {
configuration.put(
ScriptingConfigurationKeys.DISABLE_SCRIPTING_PLUGIN_OPTION,
value.takeUnless { it.isBlank() }?.toBoolean() ?: true
)
}
SCRIPT_DEFINITIONS_OPTION, LEGACY_SCRIPT_TEMPLATES_OPTION -> {
val currentDefs = configuration.getList(ScriptingConfigurationKeys.SCRIPT_DEFINITIONS).toMutableList()
currentDefs.addAll(value.split(','))
configuration.put(ScriptingConfigurationKeys.SCRIPT_DEFINITIONS, currentDefs)
}
SCRIPT_DEFINITIONS_CLASSPATH_OPTION -> {
val currentCP = configuration.getList(ScriptingConfigurationKeys.SCRIPT_DEFINITIONS_CLASSPATH).toMutableList()
currentCP.addAll(value.split(File.pathSeparatorChar).map(::File))
configuration.put(ScriptingConfigurationKeys.SCRIPT_DEFINITIONS_CLASSPATH, currentCP)
}
DISABLE_STANDARD_SCRIPT_DEFINITION_OPTION -> {
configuration.put(
JVMConfigurationKeys.DISABLE_STANDARD_SCRIPT_DEFINITION,
value.takeUnless { it.isBlank() }?.toBoolean() ?: true
)
}
DISABLE_SCRIPT_DEFINITIONS_FROM_CLSSPATH_OPTION -> {
configuration.put(
ScriptingConfigurationKeys.DISABLE_SCRIPT_DEFINITIONS_FROM_CLASSPATH_OPTION,
value.takeUnless { it.isBlank() }?.toBoolean() ?: true
)
}
LEGACY_SCRIPT_RESOLVER_ENVIRONMENT_OPTION -> {
val currentEnv = configuration.getMap(ScriptingConfigurationKeys.LEGACY_SCRIPT_RESOLVER_ENVIRONMENT_OPTION).toMutableMap()
// parses key/value pairs in the form <key>=<value>, where
// <key> - is a single word (\w+ pattern)
// <value> - optionally quoted string with allowed escaped chars (only double-quote, comma and backslash chars are supported)
// TODO: implement generic unescaping
// TODO: consider switching to simple parser - current approach is too complicated already and doesn't handle quoted commas (unless they are escaped)
val envParseRe = """(\w+)=(?:"([^"\\]*(\\.[^"\\]*)*)"|([^\s]*))""".toRegex()
val unescapeRe = """\\(["\\,])""".toRegex()
val splitRe = """(?:\\.|[^,\\]++)*""".toRegex()
val splitMatches = splitRe.findAll(value)
for (envParam in splitMatches.map { it.value }.filter { it.isNotBlank() }) {
val match = envParseRe.matchEntire(envParam)
if (match == null || match.groupValues.size < 4 || match.groupValues[1].isBlank()) {
throw CliOptionProcessingException("Unable to parse script-resolver-environment argument $envParam")
}
currentEnv[match.groupValues[1]] =
match.groupValues.drop(2).firstOrNull { it.isNotEmpty() }?.let { unescapeRe.replace(it, "\$1") }
}
configuration.put(ScriptingConfigurationKeys.LEGACY_SCRIPT_RESOLVER_ENVIRONMENT_OPTION, currentEnv)
}
else -> throw CliOptionProcessingException("Unknown option: ${option.optionName}")
}
}
@@ -0,0 +1,108 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.scripting.compiler.plugin
import com.intellij.core.CoreFileTypeRegistry
import com.intellij.mock.MockProject
import com.intellij.openapi.fileTypes.FileTypeRegistry
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.extensions.CompilerConfigurationExtension
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.script.ScriptDefinitionProvider
import org.jetbrains.kotlin.script.StandardScriptDefinition
import org.jetbrains.kotlin.scripting.compiler.plugin.definitions.ScriptDefinitionsFromClasspathDiscoverySource
import org.jetbrains.kotlin.scripting.compiler.plugin.definitions.loadScriptTemplatesFromClasspath
import org.jetbrains.kotlin.scripting.legacy.CliScriptDefinitionProvider
import java.io.File
class ScriptingCompilerConfigurationExtension(val project: MockProject) : CompilerConfigurationExtension {
override fun updateConfiguration(configuration: CompilerConfiguration) {
if (!configuration.getBoolean(ScriptingConfigurationKeys.DISABLE_SCRIPTING_PLUGIN_OPTION)) {
val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY) ?: MessageCollector.NONE
val projectRoot = project.run { basePath ?: baseDir?.canonicalPath }?.let(::File)
if (projectRoot != null) {
configuration.put(
ScriptingConfigurationKeys.LEGACY_SCRIPT_RESOLVER_ENVIRONMENT_OPTION,
"projectRoot",
projectRoot
)
}
val scriptResolverEnv = configuration.getMap(ScriptingConfigurationKeys.LEGACY_SCRIPT_RESOLVER_ENVIRONMENT_OPTION)
val explicitScriptDefinitions = configuration.getList(ScriptingConfigurationKeys.SCRIPT_DEFINITIONS)
if (explicitScriptDefinitions.isNotEmpty()) {
configureScriptDefinitions(
explicitScriptDefinitions,
configuration,
this::class.java.classLoader,
messageCollector,
scriptResolverEnv
)
}
// If not disabled explicitly, we should always support at least the standard script definition
if (!configuration.getBoolean(JVMConfigurationKeys.DISABLE_STANDARD_SCRIPT_DEFINITION) &&
!configuration.getList(JVMConfigurationKeys.SCRIPT_DEFINITIONS).contains(StandardScriptDefinition)
) {
configuration.add(JVMConfigurationKeys.SCRIPT_DEFINITIONS, StandardScriptDefinition)
}
configuration.add(
JVMConfigurationKeys.SCRIPT_DEFINITIONS_SOURCES,
ScriptDefinitionsFromClasspathDiscoverySource(
configuration.jvmClasspathRoots,
configuration.get(ScriptingConfigurationKeys.LEGACY_SCRIPT_RESOLVER_ENVIRONMENT_OPTION) ?: emptyMap(),
messageCollector
)
)
}
// If not disabled explicitly, we should always support at least the standard script definition
if (!configuration.getBoolean(JVMConfigurationKeys.DISABLE_STANDARD_SCRIPT_DEFINITION) &&
StandardScriptDefinition !in configuration.getList(JVMConfigurationKeys.SCRIPT_DEFINITIONS)
) {
configuration.add(JVMConfigurationKeys.SCRIPT_DEFINITIONS, StandardScriptDefinition)
}
val scriptDefinitionProvider = ScriptDefinitionProvider.getInstance(project) as? CliScriptDefinitionProvider
if (scriptDefinitionProvider != null) {
scriptDefinitionProvider.setScriptDefinitionsSources(configuration.getList(JVMConfigurationKeys.SCRIPT_DEFINITIONS_SOURCES))
scriptDefinitionProvider.setScriptDefinitions(configuration.getList(JVMConfigurationKeys.SCRIPT_DEFINITIONS))
// Register new file extensions
val fileTypeRegistry = FileTypeRegistry.getInstance() as CoreFileTypeRegistry
scriptDefinitionProvider.getKnownFilenameExtensions().filter {
fileTypeRegistry.getFileTypeByExtension(it) != KotlinFileType.INSTANCE
}.forEach {
fileTypeRegistry.registerFileType(KotlinFileType.INSTANCE, it)
}
}
}
}
fun configureScriptDefinitions(
scriptTemplates: List<String>,
configuration: CompilerConfiguration,
baseClassloader: ClassLoader,
messageCollector: MessageCollector,
scriptResolverEnv: Map<String, Any?>
) {
// TODO: consider using escaping to allow kotlin escaped names in class names
val templatesFromClasspath = loadScriptTemplatesFromClasspath(
scriptTemplates, configuration.jvmClasspathRoots, emptyList(), baseClassloader, scriptResolverEnv, messageCollector
)
configuration.addAll(JVMConfigurationKeys.SCRIPT_DEFINITIONS, templatesFromClasspath.toList())
}
@@ -0,0 +1,31 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.scripting.compiler.plugin
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.extensions.SyntheticResolveExtension
import org.jetbrains.kotlin.resolve.lazy.LazyClassContext
import org.jetbrains.kotlin.resolve.lazy.ResolveSession
import org.jetbrains.kotlin.resolve.lazy.declarations.PackageMemberDeclarationProvider
import org.jetbrains.kotlin.scripting.compiler.plugin.resolve.LazyScriptDescriptor
class ScriptingResolveExtension : SyntheticResolveExtension {
override fun generateSyntheticClasses(
thisDescriptor: PackageFragmentDescriptor,
name: Name,
ctx: LazyClassContext,
declarationProvider: PackageMemberDeclarationProvider,
result: MutableSet<ClassDescriptor>
) {
declarationProvider.getScriptDeclarations(name).mapTo(result) {
LazyScriptDescriptor(ctx as ResolveSession, thisDescriptor, name, it)
}
super.generateSyntheticClasses(thisDescriptor, name, ctx, declarationProvider, result)
}
}
@@ -0,0 +1,113 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.scripting.compiler.plugin.definitions
import com.intellij.openapi.fileTypes.LanguageFileType
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.name.NameUtils
import org.jetbrains.kotlin.psi.KtScript
import org.jetbrains.kotlin.script.KotlinScriptDefinition
import kotlin.reflect.KClass
import kotlin.reflect.KType
import kotlin.reflect.full.starProjectedType
import kotlin.script.experimental.api.*
import kotlin.script.experimental.dependencies.DependenciesResolver
import kotlin.script.experimental.host.ScriptingHostConfiguration
import kotlin.script.experimental.host.getScriptingClass
import kotlin.script.experimental.jvm.impl.BridgeDependenciesResolver
import kotlin.script.experimental.location.ScriptExpectedLocation
import kotlin.script.experimental.util.getOrError
// temporary trick with passing Any as a template and overwriting it below, TODO: fix after introducing new script definitions hierarchy
abstract class KotlinScriptDefinitionAdapterFromNewAPIBase : KotlinScriptDefinition(Any::class) {
abstract val scriptCompilationConfiguration: ScriptCompilationConfiguration
abstract val hostConfiguration: ScriptingHostConfiguration
open val baseClass: KClass<*> by lazy(LazyThreadSafetyMode.PUBLICATION) {
getScriptingClass(scriptCompilationConfiguration.getOrError(ScriptCompilationConfiguration.baseClass))
}
override val template: KClass<*> get() = baseClass
override val name: String
get() = scriptCompilationConfiguration[ScriptCompilationConfiguration.displayName] ?: "Kotlin Script"
override val fileType: LanguageFileType = KotlinFileType.INSTANCE
override fun isScript(fileName: String): Boolean =
fileName.endsWith(".$fileExtension")
override fun getScriptName(script: KtScript): Name {
val fileBasedName = NameUtils.getScriptNameForFile(script.containingKtFile.name)
return Name.identifier(fileBasedName.identifier.removeSuffix(".$fileExtension"))
}
override val annotationsForSamWithReceivers: List<String>
get() = emptyList()
override val dependencyResolver: DependenciesResolver by lazy(LazyThreadSafetyMode.PUBLICATION) {
BridgeDependenciesResolver(scriptCompilationConfiguration)
}
override val acceptedAnnotations: List<KClass<out Annotation>> by lazy(LazyThreadSafetyMode.PUBLICATION) {
scriptCompilationConfiguration[ScriptCompilationConfiguration.refineConfigurationOnAnnotations]?.annotations
.orEmpty()
.map { getScriptingClass(it) as KClass<out Annotation> }
}
override val implicitReceivers: List<KType> by lazy(LazyThreadSafetyMode.PUBLICATION) {
scriptCompilationConfiguration[ScriptCompilationConfiguration.implicitReceivers]
.orEmpty()
.map { getScriptingClass(it).starProjectedType }
}
override val providedProperties: List<Pair<String, KType>> by lazy(LazyThreadSafetyMode.PUBLICATION) {
scriptCompilationConfiguration[ScriptCompilationConfiguration.providedProperties]
?.map { (k, v) -> k to getScriptingClass(v).starProjectedType }.orEmpty()
}
override val additionalCompilerArguments: List<String>
get() = scriptCompilationConfiguration[ScriptCompilationConfiguration.compilerOptions]
.orEmpty()
override val scriptExpectedLocations: List<ScriptExpectedLocation>
get() = scriptCompilationConfiguration[ScriptCompilationConfiguration.ide.acceptedLocations]?.map {
when(it) {
ScriptAcceptedLocation.Sources -> ScriptExpectedLocation.SourcesOnly
ScriptAcceptedLocation.Tests -> ScriptExpectedLocation.TestsOnly
ScriptAcceptedLocation.Libraries -> ScriptExpectedLocation.Libraries
ScriptAcceptedLocation.Project -> ScriptExpectedLocation.Project
ScriptAcceptedLocation.Everywhere -> ScriptExpectedLocation.Everywhere
}
} ?: listOf(ScriptExpectedLocation.SourcesOnly, ScriptExpectedLocation.TestsOnly)
private val scriptingClassGetter by lazy(LazyThreadSafetyMode.PUBLICATION) {
hostConfiguration[ScriptingHostConfiguration.getScriptingClass]
?: throw IllegalArgumentException("Expecting 'getScriptingClass' property in the scripting environment")
}
private fun getScriptingClass(type: KotlinType) =
scriptingClassGetter(
type,
KotlinScriptDefinition::class, // Assuming that the KotlinScriptDefinition class is loaded in the proper classloader
hostConfiguration
)
}
class KotlinScriptDefinitionAdapterFromNewAPI(
override val scriptCompilationConfiguration: ScriptCompilationConfiguration,
override val hostConfiguration: ScriptingHostConfiguration
) : KotlinScriptDefinitionAdapterFromNewAPIBase() {
override val name: String get() = scriptCompilationConfiguration[ScriptCompilationConfiguration.displayName] ?: super.name
override val fileExtension: String
get() = scriptCompilationConfiguration[ScriptCompilationConfiguration.fileExtension] ?: super.fileExtension
}
@@ -0,0 +1,76 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.scripting.compiler.plugin.definitions
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import java.io.File
import kotlin.script.experimental.annotations.KotlinScript
import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.ScriptingHostConfiguration
import kotlin.script.experimental.host.configurationDependencies
import kotlin.script.experimental.host.createCompilationConfigurationFromTemplate
import kotlin.script.experimental.jvm.JvmDependency
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
class LazyScriptDefinitionFromDiscoveredClass internal constructor(
private val annotationsFromAsm: ArrayList<BinAnnData>,
private val className: String,
private val classpath: List<File>,
private val messageCollector: MessageCollector
) : KotlinScriptDefinitionAdapterFromNewAPIBase() {
constructor(
classBytes: ByteArray,
className: String,
classpath: List<File>,
messageCollector: MessageCollector
) : this(loadAnnotationsFromClass(classBytes), className, classpath, messageCollector)
override val hostConfiguration: ScriptingHostConfiguration by lazy(LazyThreadSafetyMode.PUBLICATION) {
ScriptingHostConfiguration(defaultJvmScriptingHostConfiguration) {
configurationDependencies.append(JvmDependency(classpath))
}
}
override val scriptCompilationConfiguration: ScriptCompilationConfiguration by lazy(LazyThreadSafetyMode.PUBLICATION) {
messageCollector.report(
CompilerMessageSeverity.LOGGING,
"Configure scripting: loading script definition class $className using classpath $classpath\n. ${Thread.currentThread().stackTrace}"
)
try {
createCompilationConfigurationFromTemplate(
KotlinType(className),
hostConfiguration,
LazyScriptDefinitionFromDiscoveredClass::class
)
} catch (ex: ClassNotFoundException) {
messageCollector.report(CompilerMessageSeverity.ERROR, "Cannot find script definition class $className")
InvalidScriptDefinition
} catch (ex: Exception) {
messageCollector.report(
CompilerMessageSeverity.ERROR,
"Error processing script definition class $className: ${ex.message}\nclasspath:\n${classpath.joinToString("\n", " ")}"
)
InvalidScriptDefinition
}
}
override val fileExtension: String by lazy(LazyThreadSafetyMode.PUBLICATION) {
annotationsFromAsm.find { it.name == KotlinScript::class.simpleName }?.args
?.find { it.name == "fileExtension" }?.value
?: scriptCompilationConfiguration.let {
it[ScriptCompilationConfiguration.fileExtension] ?: super.fileExtension
}
}
override val name: String by lazy(LazyThreadSafetyMode.PUBLICATION) {
annotationsFromAsm.find { it.name == KotlinScript::class.simpleName!! }?.args?.find { it.name == "name" }?.value
?: super.name
}
}
val InvalidScriptDefinition = ScriptCompilationConfiguration()
@@ -0,0 +1,344 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.scripting.compiler.plugin.definitions
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.script.KotlinScriptDefinition
import org.jetbrains.kotlin.script.KotlinScriptDefinitionFromAnnotatedTemplate
import org.jetbrains.kotlin.script.ScriptDefinitionsSource
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import java.io.File
import java.io.IOException
import java.net.URLClassLoader
import java.util.jar.JarFile
import kotlin.coroutines.experimental.buildSequence
import kotlin.script.experimental.annotations.KotlinScript
import kotlin.script.experimental.api.KotlinType
import kotlin.script.experimental.host.createCompilationConfigurationFromTemplate
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
import kotlin.script.templates.ScriptTemplateDefinition
internal const val SCRIPT_DEFINITION_MARKERS_PATH = "META-INF/kotlin/script/templates/"
class ScriptDefinitionsFromClasspathDiscoverySource(
private val classpath: List<File>,
private val scriptResolverEnv: Map<String, Any?>,
private val messageCollector: MessageCollector
) : ScriptDefinitionsSource {
override val definitions: Sequence<KotlinScriptDefinition> = run {
discoverScriptTemplatesInClasspath(
classpath,
this::class.java.classLoader,
scriptResolverEnv,
messageCollector
)
}
}
internal fun discoverScriptTemplatesInClasspath(
classpath: List<File>,
baseClassLoader: ClassLoader,
scriptResolverEnv: Map<String, Any?>,
messageCollector: MessageCollector
): Sequence<KotlinScriptDefinition> = buildSequence {
// TODO: try to find a way to reduce classpath (and classloader) to minimal one needed to load script definition and its dependencies
val loader = LazyClasspathWithClassLoader(baseClassLoader) { classpath }
// for jar files the definition class is expected in the same jar as the discovery file
// in case of directories, the class output may come separate from the resources, so some candidates should be deffered and processed later
val defferedDirDependencies = ArrayList<File>()
val defferedDefinitionCandidates = ArrayList<String>()
for (dep in classpath) {
try {
when {
dep.isFile && dep.extension == "jar" -> { // checking for extension is the compiler current behaviour, so the same logic is implemented here
JarFile(dep).use { jar ->
if (jar.getJarEntry(SCRIPT_DEFINITION_MARKERS_PATH) != null) {
val definitionNames = jar.entries().asSequence().mapNotNull {
if (it.isDirectory || !it.name.startsWith(SCRIPT_DEFINITION_MARKERS_PATH)) null
else it.name.removePrefix(SCRIPT_DEFINITION_MARKERS_PATH)
}.toList()
val (loadedDefinitions, notFoundClasses) =
definitionNames.partitionLoadJarDefinitions(jar, loader, scriptResolverEnv, messageCollector)
if (notFoundClasses.isNotEmpty()) {
messageCollector.report(
CompilerMessageSeverity.STRONG_WARNING,
"Configure scripting: unable to find script definitions [${notFoundClasses.joinToString(", ")}]"
)
}
loadedDefinitions.forEach {
yield(it)
}
}
}
}
dep.isDirectory -> {
defferedDirDependencies.add(dep) // there is no way to know that the dependency is fully "used" so we add it to the list anyway
val discoveryDir = File(dep, SCRIPT_DEFINITION_MARKERS_PATH)
if (discoveryDir.isDirectory) {
val (foundDefinitionClasses, notFoundDefinitions) = discoveryDir.listFiles().map { it.name }
.partitionLoadDirDefinitions(dep, loader, scriptResolverEnv, messageCollector)
foundDefinitionClasses.forEach {
yield(it)
}
defferedDefinitionCandidates.addAll(notFoundDefinitions)
}
}
else -> {
// assuming that invalid classpath entries will be reported elsewhere anyway, so do not spam user with additional warnings here
messageCollector.report(CompilerMessageSeverity.LOGGING, "Configure scripting: Unknown classpath entry $dep")
}
}
} catch (e: IOException) {
messageCollector.report(
CompilerMessageSeverity.STRONG_WARNING, "Configure scripting: unable to process classpath entry $dep: $e"
)
}
}
var remainingDefinitionCandidates: List<String> = defferedDefinitionCandidates
for (dep in defferedDirDependencies) {
if (remainingDefinitionCandidates.isEmpty()) break
try {
val (foundDefinitionClasses, notFoundDefinitions) =
remainingDefinitionCandidates.partitionLoadDirDefinitions(dep, loader, scriptResolverEnv, messageCollector)
foundDefinitionClasses.forEach {
yield(it)
}
remainingDefinitionCandidates = notFoundDefinitions
} catch (e: IOException) {
messageCollector.report(
CompilerMessageSeverity.STRONG_WARNING, "Configure scripting: unable to process classpath entry $dep: $e"
)
}
}
if (remainingDefinitionCandidates.isNotEmpty()) {
messageCollector.report(
CompilerMessageSeverity.STRONG_WARNING,
"The following script definitions are not found in the classpath: [${remainingDefinitionCandidates.joinToString()}]"
)
}
}
internal fun loadScriptTemplatesFromClasspath(
scriptTemplates: List<String>,
classpath: List<File>,
dependenciesClasspath: List<File>,
baseClassLoader: ClassLoader,
scriptResolverEnv: Map<String, Any?>,
messageCollector: MessageCollector
): Sequence<KotlinScriptDefinition> =
if (scriptTemplates.isEmpty()) emptySequence()
else buildSequence {
// trying the direct classloading from baseClassloader first, since this is the most performant variant
val (initialLoadedDefinitions, initialNotFoundTemplates) = scriptTemplates.partitionMapNotNull {
loadScriptDefinition(
baseClassLoader,
it,
scriptResolverEnv,
messageCollector
)
}
initialLoadedDefinitions.forEach {
yield(it)
}
// then searching the remaining templates in the supplied classpath
var remainingTemplates = initialNotFoundTemplates
val classpathAndLoader =
LazyClasspathWithClassLoader(baseClassLoader) { classpath + dependenciesClasspath }
for (dep in classpath) {
if (remainingTemplates.isEmpty()) break
try {
val (loadedDefinitions, notFoundTemplates) = when {
dep.isFile && dep.extension == "jar" -> { // checking for extension is the compiler current behaviour, so the same logic is implemented here
JarFile(dep).use { jar ->
remainingTemplates.partitionLoadJarDefinitions(jar, classpathAndLoader, scriptResolverEnv, messageCollector)
}
}
dep.isDirectory -> {
remainingTemplates.partitionLoadDirDefinitions(dep, classpathAndLoader, scriptResolverEnv, messageCollector)
}
else -> {
// assuming that invalid classpath entries will be reported elsewhere anyway, so do not spam user with additional warnings here
messageCollector.report(CompilerMessageSeverity.LOGGING, "Configure scripting: Unknown classpath entry $dep")
DefinitionsLoadPartitionResult(
listOf(),
remainingTemplates
)
}
}
if (loadedDefinitions.isNotEmpty()) {
loadedDefinitions.forEach {
yield(it)
}
remainingTemplates = notFoundTemplates
}
} catch (e: IOException) {
messageCollector.report(
CompilerMessageSeverity.STRONG_WARNING,
"Configure scripting: unable to process classpath entry $dep: $e"
)
}
}
if (remainingTemplates.isNotEmpty()) {
messageCollector.report(
CompilerMessageSeverity.STRONG_WARNING,
"Configure scripting: unable to find script definition classes: ${remainingTemplates.joinToString(", ")}"
)
}
}
private data class DefinitionsLoadPartitionResult(
val loaded: List<KotlinScriptDefinition>,
val notFound: List<String>
)
private inline fun List<String>.partitionLoadDefinitions(
classpathAndLoader: LazyClasspathWithClassLoader,
scriptResolverEnv: Map<String, Any?>,
messageCollector: MessageCollector,
getBytes: (String) -> ByteArray?
): DefinitionsLoadPartitionResult {
val loaded = ArrayList<KotlinScriptDefinition>()
val notFound = ArrayList<String>()
for (definitionName in this) {
val classBytes = getBytes(definitionName)
val definition = classBytes?.let {
loadScriptDefinition(
it,
definitionName,
classpathAndLoader,
scriptResolverEnv,
messageCollector
)
}
when {
definition != null -> loaded.add(definition)
classBytes != null -> {}
else -> notFound.add(definitionName)
}
}
return DefinitionsLoadPartitionResult(loaded, notFound)
}
private fun List<String>.partitionLoadJarDefinitions(
jar: JarFile,
classpathAndLoader: LazyClasspathWithClassLoader,
scriptResolverEnv: Map<String, Any?>,
messageCollector: MessageCollector
): DefinitionsLoadPartitionResult = partitionLoadDefinitions(classpathAndLoader, scriptResolverEnv, messageCollector) { definitionName ->
jar.getJarEntry("${definitionName.replace('.', '/')}.class")?.let { jar.getInputStream(it).readBytes() }
}
private fun List<String>.partitionLoadDirDefinitions(
dir: File,
classpathAndLoader: LazyClasspathWithClassLoader,
scriptResolverEnv: Map<String, Any?>,
messageCollector: MessageCollector
): DefinitionsLoadPartitionResult = partitionLoadDefinitions(classpathAndLoader, scriptResolverEnv, messageCollector) { definitionName ->
File(dir, "${definitionName.replace('.', '/')}.class").takeIf { it.exists() && it.isFile }?.readBytes()
}
private fun loadScriptDefinition(
templateClassBytes: ByteArray,
templateClassName: String,
classpathAndLoader: LazyClasspathWithClassLoader,
scriptResolverEnv: Map<String, Any?>,
messageCollector: MessageCollector
): KotlinScriptDefinition? {
val anns = loadAnnotationsFromClass(templateClassBytes)
for (ann in anns) {
var def: KotlinScriptDefinition? = null
if (ann.name == KotlinScript::class.simpleName) {
def = LazyScriptDefinitionFromDiscoveredClass(
anns,
templateClassName,
classpathAndLoader.classpath,
messageCollector
)
} else if (ann.name == ScriptTemplateDefinition::class.simpleName) {
val templateClass = classpathAndLoader.classLoader.loadClass(templateClassName).kotlin
def = KotlinScriptDefinitionFromAnnotatedTemplate(templateClass, scriptResolverEnv, classpathAndLoader.classpath)
}
if (def != null) {
messageCollector.report(
CompilerMessageSeverity.LOGGING,
"Configure scripting: Added template $templateClassName from ${classpathAndLoader.classpath}"
)
return def
}
}
messageCollector.report(
CompilerMessageSeverity.STRONG_WARNING,
"Configure scripting: $templateClassName is not marked with any known kotlin script annotation"
)
return null
}
private fun loadScriptDefinition(
classLoader: ClassLoader,
template: String,
scriptResolverEnv: Map<String, Any?>,
messageCollector: MessageCollector
): KotlinScriptDefinition? {
try {
val cls = classLoader.loadClass(template)
val def =
if (cls.annotations.firstIsInstanceOrNull<KotlinScript>() != null) {
val environment = defaultJvmScriptingHostConfiguration
KotlinScriptDefinitionAdapterFromNewAPI(
createCompilationConfigurationFromTemplate(
KotlinType(cls.kotlin),
environment,
KotlinScriptDefinition::class
),
environment
)
} else {
KotlinScriptDefinitionFromAnnotatedTemplate(cls.kotlin, scriptResolverEnv)
}
messageCollector.report(
CompilerMessageSeverity.INFO,
"Added script definition $template to configuration: name = ${def.name}, " +
"resolver = ${def.dependencyResolver.javaClass.name}"
)
return def
} catch (ex: ClassNotFoundException) {
// not found - not an error, return null
} catch (ex: Exception) {
// other exceptions - might be an error
messageCollector.report(
CompilerMessageSeverity.STRONG_WARNING,
"Error on loading script definition $template: ${ex.message}"
)
}
return null
}
private class LazyClasspathWithClassLoader(baseClassLoader: ClassLoader, getClasspath: () -> List<File>) {
val classpath by lazy { getClasspath() }
val classLoader by lazy {
URLClassLoader(classpath.map { it.toURI().toURL() }.toTypedArray(), baseClassLoader)
}
}
private inline fun <T, R> Iterable<T>.partitionMapNotNull(fn: (T) -> R?): Pair<List<R>, List<T>> {
val mapped = ArrayList<R>()
val failed = ArrayList<T>()
for (v in this) {
val r = fn(v)
if (r != null) {
mapped.add(r)
} else {
failed.add(v)
}
}
return mapped to failed
}
@@ -0,0 +1,46 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.scripting.compiler.plugin.definitions
import org.jetbrains.org.objectweb.asm.*
internal class BinAnnArgData(
val name: String?,
val value: String
)
internal class BinAnnData(
val name: String,
val args: ArrayList<BinAnnArgData> = arrayListOf()
)
private class TemplateAnnotationVisitor(val anns: ArrayList<BinAnnData> = arrayListOf()) : AnnotationVisitor(Opcodes.API_VERSION) {
override fun visit(name: String?, value: Any?) {
anns.last().args.add(BinAnnArgData(name, value.toString()))
}
}
private class TemplateClassVisitor(val annVisitor: TemplateAnnotationVisitor) : ClassVisitor(Opcodes.API_VERSION) {
override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? {
val shortName = Type.getType(desc).internalName.substringAfterLast("/")
if (shortName.startsWith("KotlinScript") || shortName.startsWith("ScriptTemplate")) {
annVisitor.anns.add(BinAnnData(shortName))
return annVisitor
}
return null
}
}
internal fun loadAnnotationsFromClass(fileContents: ByteArray): ArrayList<BinAnnData> {
val visitor =
TemplateClassVisitor(TemplateAnnotationVisitor())
ClassReader(fileContents).accept(visitor, ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES)
return visitor.annVisitor.anns
}
@@ -0,0 +1,55 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.scripting.compiler.plugin.definitions
import com.intellij.ide.highlighter.JavaClassFileType
import com.intellij.openapi.fileTypes.FileTypeRegistry
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.idea.KotlinFileType
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.script.KotlinScriptDefinition
import org.jetbrains.kotlin.script.ScriptDefinitionProvider
fun PsiFile.scriptDefinition(): KotlinScriptDefinition? {
// 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 scriptDefinitionByFileName(project, file.name)
}
fun VirtualFile.findScriptDefinition(project: Project): KotlinScriptDefinition? {
if (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 ((PsiManager.getInstance(project).findFile(this) as? KtFile)/*?.script*/ == null) return null
return scriptDefinitionByFileName(project, name)
}
fun scriptDefinitionByFileName(project: Project, fileName: String): KotlinScriptDefinition {
val scriptDefinitionProvider = ScriptDefinitionProvider.getInstance(project) ?: return null
?: throw IllegalStateException("Unable to get script definition: ScriptDefinitionProvider is not configured.")
return scriptDefinitionProvider.findScriptDefinition(fileName) ?: scriptDefinitionProvider.getDefaultScriptDefinition()
}
private fun VirtualFile.isNonScript(): Boolean =
isDirectory ||
extension == KotlinFileType.EXTENSION ||
extension == JavaClassFileType.INSTANCE.defaultExtension ||
!this.isKotlinFileType()
private fun VirtualFile.isKotlinFileType(): Boolean {
val typeRegistry = FileTypeRegistry.getInstance()
return typeRegistry.getFileTypeByFile(this) == KotlinFileType.INSTANCE ||
typeRegistry.getFileTypeByFileName(name) == KotlinFileType.INSTANCE
}
@@ -0,0 +1,47 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.scripting.compiler.plugin
import com.intellij.mock.MockProject
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.extensions.CompilerConfigurationExtension
import org.jetbrains.kotlin.resolve.extensions.SyntheticResolveExtension
import org.jetbrains.kotlin.script.ScriptDefinitionProvider
import org.jetbrains.kotlin.script.ScriptDependenciesProvider
import org.jetbrains.kotlin.script.ScriptReportSink
import org.jetbrains.kotlin.scripting.legacy.CliScriptDefinitionProvider
import org.jetbrains.kotlin.scripting.legacy.CliScriptDependenciesProvider
import org.jetbrains.kotlin.scripting.legacy.CliScriptReportSink
class ScriptingCompilerConfigurationComponentRegistrar : ComponentRegistrar {
override fun registerProjectComponents(project: MockProject, configuration: CompilerConfiguration) {
CompilerConfigurationExtension.registerExtension(project,
ScriptingCompilerConfigurationExtension(
project
)
)
val scriptDefinitionProvider = CliScriptDefinitionProvider()
project.registerService(ScriptDefinitionProvider::class.java, scriptDefinitionProvider)
project.registerService(
ScriptDependenciesProvider::class.java,
CliScriptDependenciesProvider(project)
)
SyntheticResolveExtension.registerExtension(project,
ScriptingResolveExtension()
)
val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
if (messageCollector != null) {
project.registerService(
ScriptReportSink::class.java,
CliScriptReportSink(messageCollector)
)
}
}
}
@@ -0,0 +1,104 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.scripting.compiler.plugin.resolve
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.descriptors.impl.ClassConstructorDescriptorImpl
import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.lazy.ResolveSession
import org.jetbrains.kotlin.resolve.lazy.declarations.ClassMemberDeclarationProvider
import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyClassMemberScope
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.KotlinTypeFactory
import org.jetbrains.kotlin.types.typeUtil.asTypeProjection
class LazyScriptClassMemberScope(
resolveSession: ResolveSession,
declarationProvider: ClassMemberDeclarationProvider,
private val scriptDescriptor: LazyScriptDescriptor,
trace: BindingTrace
) : LazyClassMemberScope(resolveSession, declarationProvider, scriptDescriptor, trace) {
private val scriptPrimaryConstructor: () -> ClassConstructorDescriptorImpl? = resolveSession.storageManager.createNullableLazyValue {
val baseClass = scriptDescriptor.baseClassDescriptor()
val baseConstructorDescriptor = baseClass?.unsubstitutedPrimaryConstructor
if (baseConstructorDescriptor != null) {
val implicitReceiversParamTypes =
scriptDescriptor.implicitReceivers.mapIndexed { idx, receiver ->
val name =
if (receiver is ScriptDescriptor) "$IMPORTED_SCRIPT_PARAM_NAME_PREFIX${receiver.name}"
else "$IMPLICIT_RECEIVER_PARAM_NAME_PREFIX$idx"
name to receiver.defaultType
}
val providedPropertiesParamTypes =
scriptDescriptor.scriptProvidedProperties.map {
it.name.identifier to it.type
}
val annotations = baseConstructorDescriptor.annotations
val constructorDescriptor = ClassConstructorDescriptorImpl.create(
scriptDescriptor, annotations, baseConstructorDescriptor.isPrimary, scriptDescriptor.source
)
var paramsIndexBase = baseConstructorDescriptor.valueParameters.lastIndex + 1
val syntheticParameters =
(implicitReceiversParamTypes + providedPropertiesParamTypes).map { param: Pair<String, KotlinType> ->
ValueParameterDescriptorImpl(
constructorDescriptor,
null,
paramsIndexBase++,
Annotations.EMPTY,
Name.identifier(param.first),
param.second,
false, false, false, null, SourceElement.NO_SOURCE
)
}
val parameters = baseConstructorDescriptor.valueParameters.map { it.copy(constructorDescriptor, it.name, it.index) } +
syntheticParameters
constructorDescriptor.initialize(parameters, baseConstructorDescriptor.visibility)
constructorDescriptor.returnType = scriptDescriptor.defaultType
constructorDescriptor
} else {
null
}
}
override fun resolvePrimaryConstructor(): ClassConstructorDescriptor? {
val constructor = scriptPrimaryConstructor()
?: ClassConstructorDescriptorImpl.create(
scriptDescriptor,
Annotations.EMPTY,
true,
SourceElement.NO_SOURCE
).initialize(
emptyList(),
Visibilities.PUBLIC
)
setDeferredReturnType(constructor)
return constructor
}
override fun getNonDeclaredProperties(name: Name, result: MutableSet<PropertyDescriptor>) {
super.getNonDeclaredProperties(name, result)
if (scriptDescriptor.resultFieldName() == name.asString()) {
scriptDescriptor.resultValue?.let {
result.add(it)
}
}
}
override fun createPropertiesFromPrimaryConstructorParameters(name: Name, result: MutableSet<PropertyDescriptor>) {
}
companion object {
const val IMPLICIT_RECEIVER_PARAM_NAME_PREFIX = "\$\$implicitReceiver"
const val IMPORTED_SCRIPT_PARAM_NAME_PREFIX = "\$\$importedScript"
}
}
private fun ClassDescriptor.substitute(vararg types: KotlinType): KotlinType? =
KotlinTypeFactory.simpleType(this.defaultType, arguments = types.map { it.asTypeProjection() })
@@ -0,0 +1,254 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.scripting.compiler.plugin.resolve
import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiManager
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.descriptors.annotations.FilteredAnnotations
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.diagnostics.Errors.MISSING_IMPORTED_SCRIPT_FILE
import org.jetbrains.kotlin.diagnostics.Errors.MISSING_IMPORTED_SCRIPT_PSI
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getChildOfType
import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.resolve.lazy.LazyClassContext
import org.jetbrains.kotlin.resolve.lazy.ResolveSession
import org.jetbrains.kotlin.resolve.lazy.data.KtScriptInfo
import org.jetbrains.kotlin.resolve.lazy.declarations.ClassMemberDeclarationProvider
import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyClassDescriptor
import org.jetbrains.kotlin.resolve.scopes.LexicalScope
import org.jetbrains.kotlin.resolve.scopes.LexicalScopeImpl
import org.jetbrains.kotlin.resolve.scopes.LexicalScopeKind
import org.jetbrains.kotlin.resolve.source.toSourceElement
import org.jetbrains.kotlin.script.KotlinScriptDefinition
import org.jetbrains.kotlin.script.ScriptDependenciesProvider
import org.jetbrains.kotlin.script.ScriptPriorities
import org.jetbrains.kotlin.scripting.compiler.plugin.definitions.scriptDefinitionByFileName
import org.jetbrains.kotlin.types.TypeSubstitutor
import org.jetbrains.kotlin.types.typeUtil.isNothing
import org.jetbrains.kotlin.types.typeUtil.isUnit
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import java.io.File
import kotlin.reflect.KClass
import kotlin.reflect.KType
class LazyScriptDescriptor(
val resolveSession: ResolveSession,
containingDeclaration: DeclarationDescriptor,
name: Name,
internal val scriptInfo: KtScriptInfo
) : ScriptDescriptor, LazyClassDescriptor(
resolveSession,
containingDeclaration,
name,
scriptInfo,
/* isExternal = */ false
) {
init {
resolveSession.trace.record(BindingContext.SCRIPT, scriptInfo.script, this)
}
override fun getResultValue(): ReplResultPropertyDescriptor? {
val expression = scriptInfo.script
.getChildOfType<KtBlockExpression>()
?.getChildrenOfType<KtScriptInitializer>()?.lastOrNull()
?.getChildOfType<KtExpression>()
val type = expression?.let {
resolveSession.trace.bindingContext.getType(it)
}
return if (type != null && !type.isUnit() && !type.isNothing()) {
resultFieldName()?.let {
ReplResultPropertyDescriptor(
Name.identifier(it),
type,
this.thisAsReceiverParameter,
this,
expression.toSourceElement()
)
}
} else null
}
fun resultFieldName(): String? {
val scriptPriority = scriptInfo.script.getUserData(ScriptPriorities.PRIORITY_KEY)
if (scriptPriority != null) {
return "res$scriptPriority"
}
val scriptName = name.asString()
return if (scriptName.startsWith("Line_")) {
"res${scriptName.split("_")[1]}"
} else "\$\$result"
}
private val sourceElement = scriptInfo.script.toSourceElement()
override fun getSource() = sourceElement
private val priority: Int = ScriptPriorities.getScriptPriority(scriptInfo.script)
override fun getPriority() = priority
val scriptDefinition: () -> KotlinScriptDefinition = resolveSession.storageManager.createLazyValue {
scriptDefinitionByFileName(resolveSession.project, scriptInfo.script.containingKtFile.name)
}
override fun substitute(substitutor: TypeSubstitutor) = this
override fun <R, D> accept(visitor: DeclarationDescriptorVisitor<R, D>, data: D): R =
visitor.visitScriptDescriptor(this, data)
override fun createMemberScope(c: LazyClassContext, declarationProvider: ClassMemberDeclarationProvider): LazyScriptClassMemberScope =
LazyScriptClassMemberScope(
// Must be a ResolveSession for scripts
c as ResolveSession,
declarationProvider,
this,
c.trace
)
override fun getUnsubstitutedPrimaryConstructor() = super.getUnsubstitutedPrimaryConstructor()!!
internal val baseClassDescriptor: () -> ClassDescriptor? = resolveSession.storageManager.createNullableLazyValue {
val template = scriptDefinition().template
findTypeDescriptor(
template,
if (template.qualifiedName?.startsWith("kotlin.script.templates.standard") == true) Errors.MISSING_SCRIPT_STANDARD_TEMPLATE
else Errors.MISSING_SCRIPT_BASE_CLASS
)
}
override fun computeSupertypes() = listOf(baseClassDescriptor()?.defaultType ?: builtIns.anyType)
private inner class ImportedScriptDescriptorsFinder {
val fileManager = VirtualFileManager.getInstance()
val localFS = fileManager.getFileSystem(StandardFileSystems.FILE_PROTOCOL)
val psiManager = PsiManager.getInstance(scriptInfo.script.project)
operator fun invoke(importedScriptFile: File): ScriptDescriptor? {
fun errorDescriptor(errorDiagnostic: DiagnosticFactory1<PsiElement, String>?): ScriptDescriptor? {
reportErrorString1(errorDiagnostic, importedScriptFile.path)
return null
}
val vfile = localFS.findFileByPath(importedScriptFile.path)
?: return errorDescriptor(MISSING_IMPORTED_SCRIPT_FILE)
val psiFile = psiManager.findFile(vfile)
?: return errorDescriptor(MISSING_IMPORTED_SCRIPT_PSI)
// Note: is not an error now - if import references other valid source file, it is simply compiled along with script
// TODO: check if this is the behavior we want to have - see #KT-28916
val ktScript = (psiFile as? KtFile)?.declarations?.firstIsInstanceOrNull<KtScript>()
?: return null
return resolveSession.getScriptDescriptor(ktScript) as ScriptDescriptor
}
}
private val scriptImplicitReceivers: () -> List<ClassDescriptor> = resolveSession.storageManager.createLazyValue {
val res = ArrayList<ClassDescriptor>()
val importedScriptsFiles = ScriptDependenciesProvider.getInstance(scriptInfo.script.project)
?.getScriptDependencies(scriptInfo.script.containingKtFile)?.scripts
if (importedScriptsFiles != null) {
val findImportedScriptDescriptor = ImportedScriptDescriptorsFinder()
importedScriptsFiles.mapNotNullTo(res) {
findImportedScriptDescriptor(it)
}
}
scriptDefinition().implicitReceivers.mapNotNullTo(res) { receiver ->
findTypeDescriptor(receiver, Errors.MISSING_SCRIPT_RECEIVER_CLASS)
}
res
}
internal fun findTypeDescriptor(kClass: KClass<*>, errorDiagnostic: DiagnosticFactory1<PsiElement, String>?): ClassDescriptor? =
findTypeDescriptor(kClass.classId, kClass.toString(), errorDiagnostic)
internal fun findTypeDescriptor(type: KType, errorDiagnostic: DiagnosticFactory1<PsiElement, String>?): ClassDescriptor? =
findTypeDescriptor(type.classId, type.toString(), errorDiagnostic)
internal fun findTypeDescriptor(
classId: ClassId?, typeName: String,
errorDiagnostic: DiagnosticFactory1<PsiElement, String>?
): ClassDescriptor? {
val typeDescriptor = classId?.let { module.findClassAcrossModuleDependencies(it) }
if (typeDescriptor == null) {
reportErrorString1(errorDiagnostic, classId?.asSingleFqName()?.toString() ?: typeName)
}
return typeDescriptor
}
private fun reportErrorString1(errorDiagnostic: DiagnosticFactory1<PsiElement, String>?, arg: String) {
if (errorDiagnostic != null) {
// TODO: use PositioningStrategies to highlight some specific place in case of error, instead of treating the whole file as invalid
resolveSession.trace.report(
errorDiagnostic.on(
scriptInfo.script,
arg
)
)
}
}
override fun getImplicitReceivers(): List<ClassDescriptor> = scriptImplicitReceivers()
private val scriptProvidedProperties: () -> ScriptProvidedPropertiesDescriptor = resolveSession.storageManager.createLazyValue {
ScriptProvidedPropertiesDescriptor(this)
}
override fun getScriptProvidedProperties(): List<PropertyDescriptor> = scriptProvidedProperties().properties()
private val scriptOuterScope: () -> LexicalScope = resolveSession.storageManager.createLazyValue {
var outerScope = super.getOuterScope()
val outerScopeReceivers = implicitReceivers.let {
if (scriptDefinition().providedProperties.isEmpty()) {
it
} else {
it + ScriptProvidedPropertiesDescriptor(this)
}
}
for (receiverClassDescriptor in outerScopeReceivers.asReversed()) {
outerScope = LexicalScopeImpl(
outerScope,
receiverClassDescriptor,
true,
receiverClassDescriptor.thisAsReceiverParameter,
LexicalScopeKind.CLASS_MEMBER_SCOPE
)
}
outerScope
}
override fun getOuterScope(): LexicalScope = scriptOuterScope()
private val scriptClassAnnotations: () -> Annotations = resolveSession.storageManager.createLazyValue {
baseClassDescriptor()?.annotations?.let { ann ->
FilteredAnnotations(ann) { fqname ->
val shortName = fqname.shortName().identifier
// TODO: consider more precise annotation filtering
!shortName.startsWith("KotlinScript") && !shortName.startsWith("ScriptTemplate")
}
} ?: super.annotations
}
override val annotations: Annotations
get() = scriptClassAnnotations()
}
@@ -0,0 +1,39 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.scripting.compiler.plugin.resolve
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.descriptors.impl.PropertyDescriptorImpl
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.types.KotlinType
class ReplResultPropertyDescriptor(
name: Name,
kotlinType: KotlinType,
receiver: ReceiverParameterDescriptor?,
script: ScriptDescriptor,
source: SourceElement
) : PropertyDescriptorImpl(
script,
null,
Annotations.EMPTY,
Modality.FINAL,
Visibilities.PUBLIC,
false,
name,
CallableMemberDescriptor.Kind.SYNTHESIZED,
source,
/* lateInit = */ false, /* isConst = */ false, /* isExpect = */ false, /* isActual = */ false, /* isExternal = */ false,
/* isDelegated = */ false
) {
init {
setType(kotlinType, emptyList(), receiver, null)
initialize(
null, null
)
}
}
@@ -0,0 +1,77 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.scripting.compiler.plugin.resolve
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.impl.MutableClassDescriptor
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.incremental.components.LookupLocation
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.resolve.scopes.MemberScope
import org.jetbrains.kotlin.resolve.scopes.MemberScopeImpl
import org.jetbrains.kotlin.storage.LockBasedStorageManager
import org.jetbrains.kotlin.utils.Printer
class ScriptProvidedPropertiesDescriptor(script: LazyScriptDescriptor) :
MutableClassDescriptor(
script,
ClassKind.CLASS, false, false,
Name.special("<synthetic script provided properties for ${script.name}>"),
SourceElement.NO_SOURCE,
LockBasedStorageManager.NO_LOCKS
) {
init {
modality = Modality.FINAL
visibility = Visibilities.PUBLIC
setTypeParameterDescriptors(emptyList())
createTypeConstructor()
}
private val memberScope: () -> ScriptProvidedPropertiesMemberScope = script.resolveSession.storageManager.createLazyValue {
ScriptProvidedPropertiesMemberScope(
script.name.identifier,
properties()
)
}
override fun getUnsubstitutedMemberScope(): MemberScope = memberScope()
val properties: () -> List<ScriptProvidedPropertyDescriptor> = script.resolveSession.storageManager.createLazyValue {
script.scriptDefinition().providedProperties.mapNotNull { (name, type) ->
script.findTypeDescriptor(type, Errors.MISSING_SCRIPT_PROVIDED_PROPERTY_CLASS)?.let {
name to it
}
}.map { (name, classDescriptor) ->
ScriptProvidedPropertyDescriptor(
Name.identifier(name),
classDescriptor,
thisAsReceiverParameter,
true,
script
)
}
}
private class ScriptProvidedPropertiesMemberScope(
private val scriptId: String,
private val providedProperties: List<PropertyDescriptor>
) : MemberScopeImpl() {
override fun getContributedDescriptors(
kindFilter: DescriptorKindFilter,
nameFilter: (Name) -> Boolean
): Collection<DeclarationDescriptor> =
providedProperties
override fun getContributedVariables(name: Name, location: LookupLocation): Collection<PropertyDescriptor> =
providedProperties.filter { it.name == name }
override fun printScopeStructure(p: Printer) {
p.println("Scope of script provided properties: $scriptId")
}
}
}
@@ -0,0 +1,91 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.scripting.compiler.plugin.resolve
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.descriptors.impl.PropertyDescriptorImpl
import org.jetbrains.kotlin.descriptors.impl.PropertyGetterDescriptorImpl
import org.jetbrains.kotlin.descriptors.impl.PropertySetterDescriptorImpl
import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl
import org.jetbrains.kotlin.name.Name
class ScriptProvidedPropertyDescriptor(
name: Name,
typeDescriptor: ClassDescriptor,
receiver: ReceiverParameterDescriptor?,
isVar: Boolean,
script: ScriptDescriptor
) : PropertyDescriptorImpl(
script,
null,
Annotations.EMPTY,
Modality.FINAL,
Visibilities.PRIVATE,
isVar,
name,
CallableMemberDescriptor.Kind.SYNTHESIZED,
SourceElement.NO_SOURCE,
/* lateInit = */ false, /* isConst = */ false, /* isExpect = */ false, /* isActual = */ false, /* isExternal = */ false,
/* isDelegated = */ false
) {
init {
setType(typeDescriptor.defaultType, emptyList(), receiver, null)
initialize(
makePropertyGetterDescriptor(),
if (!isVar) null else makePropertySetterDescriptor()
)
}
}
private fun PropertyDescriptorImpl.makePropertyGetterDescriptor() =
PropertyGetterDescriptorImpl(
this,
Annotations.EMPTY,
this.modality,
this.visibility,
/* isDefault = */
false, /* isExternal = */
false, /* isInline = */
false,
this.kind,
null,
SourceElement.NO_SOURCE
).also {
it.initialize(returnType)
}
private fun PropertyDescriptorImpl.makePropertySetterDescriptor() =
PropertySetterDescriptorImpl(
this,
Annotations.EMPTY,
this.modality,
this.visibility,
/* isDefault = */
false, /* isExternal = */
false, /* isInline = */
false,
this.kind,
null,
SourceElement.NO_SOURCE
).also {
it.initialize(
ValueParameterDescriptorImpl(
this,
null,
0,
Annotations.EMPTY,
Name.special("<set-?>"),
returnType,
/* declaresDefaultValue = */
false, /* isCrossinline = */
false, /* isNoinline = */
false,
null,
SourceElement.NO_SOURCE
)
)
}
@@ -0,0 +1,19 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.scripting.compiler.plugin.resolve
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import kotlin.reflect.KClass
import kotlin.reflect.KType
val KClass<*>.classId: ClassId
get() = this.java.enclosingClass?.kotlin?.classId?.createNestedClassId(Name.identifier(simpleName!!))
?: ClassId.topLevel(FqName(qualifiedName!!))
val KType.classId: ClassId?
get() = classifier?.let { it as? KClass<*> }?.classId
@@ -0,0 +1,39 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.scripting.legacy
import org.jetbrains.kotlin.script.KotlinScriptDefinition
import org.jetbrains.kotlin.script.ScriptDefinitionsSource
import org.jetbrains.kotlin.script.StandardScriptDefinition
import kotlin.concurrent.write
class CliScriptDefinitionProvider : LazyScriptDefinitionProvider() {
private val definitionsFromSources: MutableList<Sequence<KotlinScriptDefinition>> = arrayListOf()
private val definitions: MutableList<KotlinScriptDefinition> = arrayListOf(StandardScriptDefinition)
override val currentDefinitions: Sequence<KotlinScriptDefinition> =
definitionsFromSources.asSequence().flatMap { it } + definitions.asSequence()
override fun getDefaultScriptDefinition(): KotlinScriptDefinition {
return StandardScriptDefinition
}
fun setScriptDefinitions(newDefinitions: List<KotlinScriptDefinition>) {
lock.write {
definitions.clear()
definitions.addAll(newDefinitions)
}
}
fun setScriptDefinitionsSources(newSources: List<ScriptDefinitionsSource>) {
lock.write {
definitionsFromSources.clear()
for (it in newSources) {
definitionsFromSources.add(it.definitions.constrainOnce())
}
}
}
}
@@ -0,0 +1,68 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.scripting.legacy
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.script.ScriptContentLoader
import org.jetbrains.kotlin.script.ScriptDependenciesProvider
import org.jetbrains.kotlin.script.ScriptReportSink
import org.jetbrains.kotlin.script.adjustByDefinition
import org.jetbrains.kotlin.scripting.compiler.plugin.definitions.findScriptDefinition
import java.io.File
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.write
import kotlin.script.experimental.dependencies.ScriptDependencies
class CliScriptDependenciesProvider(private val project: Project) : ScriptDependenciesProvider {
private val cacheLock = ReentrantReadWriteLock()
private val cache = hashMapOf<String, ScriptDependencies?>()
private val scriptContentLoader = ScriptContentLoader(project)
override fun getScriptDependencies(file: VirtualFile): ScriptDependencies? = cacheLock.read {
calculateExternalDependencies(file)
}
private fun calculateExternalDependencies(file: VirtualFile): ScriptDependencies? {
val path = file.path
val cached = cache[path]
return if (cached != null) cached
else {
val scriptDef = file.findScriptDefinition(project)
if (scriptDef != null) {
val result = scriptContentLoader.loadContentsAndResolveDependencies(scriptDef, file)
ServiceManager.getService(project, ScriptReportSink::class.java)?.attachReports(file, result.reports)
val deps = result.dependencies?.adjustByDefinition(scriptDef)
if (deps != null) {
log.info("[kts] new cached deps for $path: ${deps.classpath.joinToString(File.pathSeparator)}")
}
cacheLock.write {
cache.put(path, deps)
}
deps
} else null
}
}
}
private val log = Logger.getInstance(ScriptDependenciesProvider::class.java)
@@ -0,0 +1,35 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.scripting.legacy
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.script.ScriptReportSink
import kotlin.script.experimental.dependencies.ScriptReport
internal class CliScriptReportSink(private val messageCollector: MessageCollector) : ScriptReportSink {
override fun attachReports(scriptFile: VirtualFile, reports: List<ScriptReport>) {
reports.forEach {
messageCollector.report(it.severity.convertSeverity(), it.message, location(scriptFile, it.position))
}
}
private fun location(scriptFile: VirtualFile, position: ScriptReport.Position?): CompilerMessageLocation? {
if (position == null) return CompilerMessageLocation.create(scriptFile.path)
return CompilerMessageLocation.create(scriptFile.path, position.startLine, position.startColumn, null)
}
private fun ScriptReport.Severity.convertSeverity(): CompilerMessageSeverity = when (this) {
ScriptReport.Severity.FATAL -> CompilerMessageSeverity.ERROR
ScriptReport.Severity.ERROR -> CompilerMessageSeverity.ERROR
ScriptReport.Severity.WARNING -> CompilerMessageSeverity.WARNING
ScriptReport.Severity.INFO -> CompilerMessageSeverity.INFO
ScriptReport.Severity.DEBUG -> CompilerMessageSeverity.LOGGING
}
}
@@ -0,0 +1,88 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.scripting.legacy
import com.intellij.ide.highlighter.JavaFileType
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.script.KotlinScriptDefinition
import org.jetbrains.kotlin.script.ScriptDefinitionProvider
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.write
abstract class LazyScriptDefinitionProvider : ScriptDefinitionProvider {
protected val lock = ReentrantReadWriteLock()
protected abstract val currentDefinitions: Sequence<KotlinScriptDefinition>
private var _cachedDefinitions: Sequence<KotlinScriptDefinition>? = null
private val cachedDefinitions: Sequence<KotlinScriptDefinition>
get() {
assert(lock.readLockCount > 0) { "cachedDefinitions should only be used under the read lock" }
if (_cachedDefinitions == null) lock.write {
_cachedDefinitions = CachingSequence(currentDefinitions.constrainOnce())
}
return _cachedDefinitions!!
}
protected fun clearCache() {
lock.write {
_cachedDefinitions = null
}
}
protected open fun nonScriptFileName(fileName: String) = nonScriptFilenameSuffixes.any {
fileName.endsWith(it, ignoreCase = true)
}
override fun findScriptDefinition(fileName: String): KotlinScriptDefinition? =
if (nonScriptFileName(fileName)) null
else lock.read {
cachedDefinitions.firstOrNull { it.isScript(fileName) }
}
override fun isScript(fileName: String) = findScriptDefinition(fileName) != null
override fun getKnownFilenameExtensions(): Sequence<String> = lock.read {
cachedDefinitions.map { it.fileExtension }
}
companion object {
// TODO: find a common place for storing kotlin-related extensions and reuse values from it everywhere
protected val nonScriptFilenameSuffixes = arrayOf(".${KotlinFileType.EXTENSION}", ".${JavaFileType.DEFAULT_EXTENSION}")
}
}
private class CachingSequence<T>(from: Sequence<T>) : Sequence<T> {
private val lock = ReentrantReadWriteLock()
private val sequenceIterator = from.iterator()
private val cache = arrayListOf<T>()
private inner class CachingIterator : Iterator<T> {
private var cacheCursor = 0
override fun hasNext(): Boolean =
lock.read { cacheCursor < cache.size }
// iterator's hasNext can mutate the iterator's state, therefore write lock is needed
|| lock.write { cacheCursor < cache.size || sequenceIterator.hasNext() }
override fun next(): T {
lock.read {
if (cacheCursor < cache.size) return cache[cacheCursor++]
}
// lock.write is not an upgrade but retake, therefore - one more check needed
lock.write {
return if (cacheCursor < cache.size) cache[cacheCursor++]
else sequenceIterator.next().also { cache.add(it) }
}
}
}
override fun iterator(): Iterator<T> = CachingIterator()
}
@@ -0,0 +1,5 @@
import kotlin.script.templates.*
@ScriptTemplateAdditionalCompilerArguments(["-version"])
abstract class TestScriptWithOtherAnnotation
@@ -0,0 +1,12 @@
import kotlin.script.experimental.annotations.*
import kotlin.script.experimental.api.*
import kotlin.script.experimental.util.*
object TestScriptWithReceiversDefinition : ScriptCompilationConfiguration(
{
implicitReceivers(String::class)
})
@KotlinScript(fileExtension = "1.kts", compilationConfiguration = TestScriptWithReceiversDefinition::class)
abstract class TestScriptWithReceivers
@@ -0,0 +1,13 @@
import kotlin.script.experimental.annotations.*
import kotlin.script.experimental.api.*
import kotlin.script.experimental.util.*
object TestScriptWithSimpleEnvVarsDefinition : ScriptCompilationConfiguration(
{
providedProperties("stringVar1" to String::class)
})
@KotlinScript(fileExtension = "2.kts", compilationConfiguration = TestScriptWithSimpleEnvVarsDefinition::class)
abstract class TestScriptWithSimpleEnvVars
@@ -0,0 +1,2 @@
val res = stringVar1.drop(4)
@@ -0,0 +1,2 @@
val res = drop(4)
@@ -0,0 +1,255 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.scripting.compiler.plugin
import junit.framework.TestCase
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.ExitCode
import org.jetbrains.kotlin.cli.common.config.addKotlinSourceRoots
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler
import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoot
import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.scripting.compiler.plugin.definitions.SCRIPT_DEFINITION_MARKERS_PATH
import org.jetbrains.kotlin.scripting.compiler.plugin.definitions.discoverScriptTemplatesInClasspath
import org.jetbrains.kotlin.scripting.compiler.plugin.definitions.loadScriptTemplatesFromClasspath
import org.jetbrains.kotlin.test.ConfigurationKind
import org.jetbrains.kotlin.test.KotlinTestUtils
import org.jetbrains.kotlin.test.TestCaseWithTmpdir
import org.jetbrains.kotlin.test.TestJdkKind
import org.jetbrains.kotlin.utils.KotlinPaths
import org.jetbrains.kotlin.utils.PathUtil
import org.junit.Assert
import java.io.File
class ScriptingCompilerPluginTest : TestCaseWithTmpdir() {
companion object {
const val TEST_DATA_DIR = "plugins/scripting/scripting-cli/testData"
}
private val kotlinPaths: KotlinPaths by lazy(LazyThreadSafetyMode.PUBLICATION) {
val paths = PathUtil.kotlinPathsForDistDirectory
TestCase.assertTrue("Lib directory doesn't exist. Run 'ant dist'", paths.libPath.absoluteFile.isDirectory)
paths
}
val compilerClasspath = listOf(kotlinPaths.compilerPath)
val runtimeClasspath = listOf( kotlinPaths.stdlibPath, kotlinPaths.scriptRuntimePath, kotlinPaths.reflectPath)
val scriptingClasspath = listOf("kotlin-scripting-common.jar").map { File(kotlinPaths.libPath, it) }
private fun createEnvironment(
sources: List<String>, destDir: File, messageCollector: MessageCollector, confBody: CompilerConfiguration.() -> Unit
): KotlinCoreEnvironment {
val configuration = KotlinTestUtils.newConfiguration(ConfigurationKind.NO_KOTLIN_REFLECT, TestJdkKind.FULL_JDK).apply {
put<MessageCollector>(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector)
addKotlinSourceRoots(sources)
put(JVMConfigurationKeys.OUTPUT_DIRECTORY, destDir)
confBody()
}
return KotlinCoreEnvironment.createForTests(testRootDisposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
}
fun testScriptResolverEnvironmentArgsParsing() {
val longStr = (1..100).joinToString("\\,") { """\" $it aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \\""" }
val unescapeRe = """\\(["\\,])""".toRegex()
val cmdlineProcessor = ScriptingCommandLineProcessor()
val configuration = CompilerConfiguration()
cmdlineProcessor.processOption(
ScriptingCommandLineProcessor.LEGACY_SCRIPT_RESOLVER_ENVIRONMENT_OPTION,
"""abc=def,11="ab cd \\ \"",long="$longStr"""",
configuration
)
val res = configuration.getMap(ScriptingConfigurationKeys.LEGACY_SCRIPT_RESOLVER_ENVIRONMENT_OPTION)
Assert.assertEquals(
hashMapOf("abc" to "def", "11" to "ab cd \\ \"", "long" to unescapeRe.replace(longStr, "\$1")),
res
)
}
fun testLazyScriptDefinitionDiscovery() {
// Three tests in one function: the direct loading, the discovery code separately, and as a part of regular compilation
// tests are combined to avoid multiple compilation of script definition modules
val defsOut = File(tmpdir, "testLazyScriptDefinition/out/defs")
val defsSrc = File(TEST_DATA_DIR, "lazyDefinitions/definitions")
val scriptsOut = File(tmpdir, "testLazyScriptDefinition/out/scripts")
val scriptsSrc = File(TEST_DATA_DIR, "lazyDefinitions/scripts")
val scriptsOut2 = File(tmpdir, "testLazyScriptDefinition/out/scripts2")
val defClasses = listOf("TestScriptWithReceivers", "TestScriptWithSimpleEnvVars")
val messageCollector = TestMessageCollector()
val definitionsCompileResult = KotlinToJVMBytecodeCompiler.compileBunchOfSources(
createEnvironment(defClasses.map { File(defsSrc, "$it.kt").canonicalPath }, defsOut, messageCollector) {
addJvmClasspathRoots(runtimeClasspath)
addJvmClasspathRoots(scriptingClasspath)
}
)
assertTrue(definitionsCompileResult) {
"Compilation of script definitions failed: $messageCollector"
}
messageCollector.clear()
loadScriptTemplatesFromClasspath(
listOf("TestScriptWithReceivers", "TestScriptWithSimpleEnvVars"),
listOf(defsOut), emptyList(), this::class.java.classLoader, emptyMap(), messageCollector
).toList()
for (def in defClasses) {
assertTrue(messageCollector.messages.any { it.message.contains("Configure scripting: Added template $def") }) {
"Missing messages from loading sequence (should contain \"Added template $def\"):\n$messageCollector"
}
assertTrue(messageCollector.messages.none { it.message.contains("Configure scripting: loading script definition class $def") }) {
"Unexpected messages from loading sequence (should not contain \"loading script definition class $def\"):\n$messageCollector"
}
}
messageCollector.clear()
// chacking lazy discovery
val templatesDir = File(defsOut, SCRIPT_DEFINITION_MARKERS_PATH).also { it.mkdirs() }
for (def in defClasses) {
File(templatesDir, def).createNewFile()
}
val lazyDefsSeq =
discoverScriptTemplatesInClasspath(
listOf(defsOut),
this::class.java.classLoader,
emptyMap(),
messageCollector
)
assertTrue(messageCollector.messages.isEmpty()) {
"Unexpected messages from discovery sequence (should be empty):\n$messageCollector"
}
val lazyDefs = lazyDefsSeq.toList()
for (def in defClasses) {
assertTrue(messageCollector.messages.any { it.message.contains("Configure scripting: Added template $def") }) {
"Missing messages from discovery sequence (should contain \"Added template $def\"):\n$messageCollector"
}
assertTrue(messageCollector.messages.none { it.message.contains("Configure scripting: loading script definition class $def") }) {
"Unexpected messages from discovery sequence (should not contain \"loading script definition class $def\"):\n$messageCollector"
}
}
messageCollector.clear()
val scriptFiles = scriptsSrc.listFiles { file: File -> file.extension == "kts" }.map { it.canonicalPath}
val scriptsCompileEnv = createEnvironment(scriptFiles, scriptsOut, messageCollector) {
addJvmClasspathRoots(runtimeClasspath)
addJvmClasspathRoots(scriptingClasspath)
addJvmClasspathRoot(defsOut)
addAll(JVMConfigurationKeys.SCRIPT_DEFINITIONS, lazyDefs)
}
val res = KotlinToJVMBytecodeCompiler.compileBunchOfSources(scriptsCompileEnv)
assertTrue(res) {
"Failed to compile scripts:\n$messageCollector"
}
val cp = (runtimeClasspath + scriptingClasspath + defsOut).joinToString(File.pathSeparator)
val exitCode = K2JVMCompiler().exec(System.err, "-cp", cp, *(scriptFiles.toTypedArray()), "-d", scriptsOut2.canonicalPath)
Assert.assertEquals(ExitCode.OK, exitCode)
}
fun testLazyScriptDefinitionOtherAnnotation() {
val defsOut = File(tmpdir, "testLazyScriptDefinition/out/otherAnn")
val defsSrc = File(TEST_DATA_DIR, "lazyDefinitions/definitions")
val defClasses = listOf("TestScriptWithOtherAnnotation")
val messageCollector = TestMessageCollector()
val definitionsCompileResult = KotlinToJVMBytecodeCompiler.compileBunchOfSources(
createEnvironment(defClasses.map { File(defsSrc, "$it.kt").canonicalPath }, defsOut, messageCollector) {
addJvmClasspathRoots(runtimeClasspath)
addJvmClasspathRoots(scriptingClasspath)
}
)
assertTrue(definitionsCompileResult) {
"Compilation of script definitions failed: $messageCollector"
}
val templatesDir = File(defsOut, SCRIPT_DEFINITION_MARKERS_PATH).also { it.mkdirs() }
for (def in defClasses) {
File(templatesDir, def).createNewFile()
}
messageCollector.clear()
discoverScriptTemplatesInClasspath(
listOf(defsOut),
this::class.java.classLoader,
emptyMap(),
messageCollector
).toList()
assertTrue(
messageCollector.messages.isNotEmpty()
&& messageCollector.messages.all { it.message.contains("s not marked with any known kotlin script annotation") }
) {
"Unexpected messages from discovery sequence:\n$messageCollector"
}
}
}
class TestMessageCollector : MessageCollector {
data class Message(val severity: CompilerMessageSeverity, val message: String, val location: CompilerMessageLocation?)
val messages = arrayListOf<Message>()
override fun clear() {
messages.clear()
}
override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageLocation?) {
messages.add(Message(severity, message, location))
}
override fun hasErrors(): Boolean = messages.any { it.severity == CompilerMessageSeverity.EXCEPTION || it.severity == CompilerMessageSeverity.ERROR }
override fun toString(): String {
return messages.joinToString("\n") { "${it.severity}: ${it.message}${it.location?.let{" at $it"} ?: ""}" }
}
}
fun TestMessageCollector.assertHasMessage(msg: String, desiredSeverity: CompilerMessageSeverity? = null) {
assert(messages.any { it.message.contains(msg) && (desiredSeverity == null || it.severity == desiredSeverity) }) {
"Expecting message \"$msg\" with severity ${desiredSeverity?.toString() ?: "Any"}, actual:\n" +
messages.joinToString("\n") { it.severity.toString() + ": " + it.message }
}
}
fun assertTrue(exp: Boolean, msg: () -> String) {
if (!exp) {
Assert.fail(msg())
}
}