Refactor script definition and related parts:
- simplify script definition interface, convert it to class - create simple definitions right from base - refactor (rename and simplify) script definition with annotated template - simplify usages of script definition in many places
This commit is contained in:
@@ -22,9 +22,8 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.kotlin.name.FqName;
|
||||
import org.jetbrains.kotlin.psi.stubs.KotlinScriptStub;
|
||||
import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes;
|
||||
import org.jetbrains.kotlin.script.GetScriptDefinitionKt;
|
||||
import org.jetbrains.kotlin.script.KotlinScriptDefinition;
|
||||
import org.jetbrains.kotlin.script.KotlinScriptDefinitionProvider;
|
||||
import org.jetbrains.kotlin.script.KotlinScriptDefinitionProviderKt;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -37,7 +36,7 @@ public class KtScript extends KtNamedDeclarationStub<KotlinScriptStub> implement
|
||||
private KotlinScriptDefinition getKotlinScriptDefinition() {
|
||||
if (!kotlinScriptDefinitionInitialized) {
|
||||
KtFile ktFile = getContainingKtFile();
|
||||
kotlinScriptDefinitionField = GetScriptDefinitionKt.getScriptDefinition(ktFile);
|
||||
kotlinScriptDefinitionField = KotlinScriptDefinitionProviderKt.getScriptDefinition(ktFile);
|
||||
kotlinScriptDefinitionInitialized = true;
|
||||
assert kotlinScriptDefinitionField != null : "Should not parse a script without definition: " + ktFile.toString();
|
||||
}
|
||||
|
||||
+1
@@ -24,6 +24,7 @@ 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.script.getScriptParameters
|
||||
|
||||
class LazyScriptClassMemberScope(
|
||||
resolveSession: ResolveSession,
|
||||
|
||||
+2
-11
@@ -29,8 +29,8 @@ import org.jetbrains.kotlin.resolve.lazy.declarations.ClassMemberDeclarationProv
|
||||
import org.jetbrains.kotlin.resolve.source.toSourceElement
|
||||
import org.jetbrains.kotlin.script.KotlinScriptDefinition
|
||||
import org.jetbrains.kotlin.script.ScriptPriorities
|
||||
import org.jetbrains.kotlin.script.getKotlinTypeByFqName
|
||||
import org.jetbrains.kotlin.script.getScriptDefinition
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.TypeSubstitutor
|
||||
import org.jetbrains.kotlin.utils.ifEmpty
|
||||
|
||||
@@ -79,14 +79,5 @@ class LazyScriptDescriptor(
|
||||
|
||||
override fun getUnsubstitutedPrimaryConstructor() = super.getUnsubstitutedPrimaryConstructor()!!
|
||||
|
||||
override fun computeSupertypes() = scriptDefinition.getScriptSupertypes(this).ifEmpty { listOf(builtIns.anyType) }
|
||||
|
||||
override fun getScriptParametersToPassToSuperclass(): List<Pair<Name, KotlinType>> {
|
||||
val scriptParams = scriptDefinition.getScriptParameters(this)
|
||||
return scriptDefinition.getScriptParametersToPassToSuperclass(this).map { name ->
|
||||
Pair(name,
|
||||
scriptParams.find { it.name == name }?.type
|
||||
?: throw RuntimeException("Unknown script parameter '$name' is specified as a script base class parameter"))
|
||||
}
|
||||
}
|
||||
override fun computeSupertypes() = listOf(getKotlinTypeByFqName(this, scriptDefinition.template.qualifiedName!!)).ifEmpty { listOf(builtIns.anyType) }
|
||||
}
|
||||
|
||||
@@ -18,48 +18,28 @@ package org.jetbrains.kotlin.script
|
||||
|
||||
import com.intellij.openapi.fileTypes.LanguageFileType
|
||||
import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.annotations.Annotations
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.parsing.KotlinParserDefinition
|
||||
import org.jetbrains.kotlin.psi.KtScript
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.module
|
||||
import org.jetbrains.kotlin.serialization.deserialization.NotFoundClasses
|
||||
import org.jetbrains.kotlin.serialization.deserialization.findNonGenericClassAcrossDependencies
|
||||
import org.jetbrains.kotlin.storage.LockBasedStorageManager
|
||||
import org.jetbrains.kotlin.types.*
|
||||
import java.io.File
|
||||
import java.lang.RuntimeException
|
||||
import java.lang.UnsupportedOperationException
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KType
|
||||
import kotlin.reflect.KTypeProjection
|
||||
import kotlin.reflect.KVariance
|
||||
import kotlin.script.StandardScriptTemplate
|
||||
|
||||
interface KotlinScriptDefinition {
|
||||
val name: String get() = "Kotlin Script"
|
||||
open class KotlinScriptDefinition(val template: KClass<out Any>) {
|
||||
|
||||
val template: KClass<out Any>
|
||||
open val name: String = "Kotlin Script"
|
||||
|
||||
// TODO: consider creating separate type (subtype? for kotlin scripts)
|
||||
val fileType: LanguageFileType get() = KotlinFileType.INSTANCE
|
||||
open val fileType: LanguageFileType = KotlinFileType.INSTANCE
|
||||
|
||||
fun <TF> isScript(file: TF): Boolean =
|
||||
open fun <TF> isScript(file: TF): Boolean =
|
||||
getFileName(file).endsWith(KotlinParserDefinition.STD_SCRIPT_EXT)
|
||||
|
||||
// TODO: replace these 3 functions with template property
|
||||
fun getScriptParameters(scriptDescriptor: ScriptDescriptor): List<ScriptParameter>
|
||||
fun getScriptSupertypes(scriptDescriptor: ScriptDescriptor): List<KotlinType> = emptyList()
|
||||
fun getScriptParametersToPassToSuperclass(scriptDescriptor: ScriptDescriptor): List<Name> = emptyList()
|
||||
|
||||
fun getScriptName(script: KtScript): Name =
|
||||
open fun getScriptName(script: KtScript): Name =
|
||||
ScriptNameUtil.fileNameWithExtensionStripped(script, KotlinParserDefinition.STD_SCRIPT_EXT)
|
||||
|
||||
fun <TF> getDependenciesFor(file: TF, project: Project, previousDependencies: KotlinScriptExternalDependencies?): KotlinScriptExternalDependencies? = null
|
||||
open fun <TF> getDependenciesFor(file: TF, project: Project, previousDependencies: KotlinScriptExternalDependencies?): KotlinScriptExternalDependencies? = null
|
||||
}
|
||||
|
||||
interface KotlinScriptExternalDependencies {
|
||||
@@ -70,54 +50,5 @@ interface KotlinScriptExternalDependencies {
|
||||
val scripts: Iterable<File> get() = emptyList()
|
||||
}
|
||||
|
||||
class KotlinScriptExternalDependenciesUnion(val dependencies: Iterable<KotlinScriptExternalDependencies>) : KotlinScriptExternalDependencies {
|
||||
override val javaHome: String? get() = dependencies.firstOrNull { it.javaHome != null }?.javaHome
|
||||
override val classpath: Iterable<File> get() = dependencies.flatMap { it.classpath }
|
||||
override val imports: Iterable<String> get() = dependencies.flatMap { it.imports }
|
||||
override val sources: Iterable<File> get() = dependencies.flatMap { it.sources }
|
||||
override val scripts: Iterable<File> get() = dependencies.flatMap { it.scripts }
|
||||
}
|
||||
object StandardScriptDefinition : KotlinScriptDefinition(StandardScriptTemplate::class)
|
||||
|
||||
data class ScriptParameter(val name: Name, val type: KotlinType)
|
||||
|
||||
object StandardScriptDefinition : KotlinScriptDefinitionFromTemplate(StandardScriptTemplate::class)
|
||||
|
||||
fun getKotlinType(scriptDescriptor: ScriptDescriptor, kClass: KClass<out Any>): KotlinType =
|
||||
getKotlinTypeByFqName(scriptDescriptor,
|
||||
kClass.qualifiedName ?: throw RuntimeException("Cannot get FQN from $kClass"))
|
||||
|
||||
fun getKotlinTypeByFqName(scriptDescriptor: ScriptDescriptor, fqName: String): KotlinType =
|
||||
scriptDescriptor.module.findNonGenericClassAcrossDependencies(
|
||||
ClassId.topLevel(FqName(fqName)),
|
||||
NotFoundClasses(LockBasedStorageManager.NO_LOCKS, scriptDescriptor.module)
|
||||
).defaultType
|
||||
|
||||
// TODO: support star projections
|
||||
// TODO: support annotations on types and type parameters
|
||||
// TODO: support type parameters on types and type projections
|
||||
fun getKotlinTypeByKType(scriptDescriptor: ScriptDescriptor, kType: KType): KotlinType {
|
||||
val classifier = kType.classifier
|
||||
if (classifier !is KClass<*>)
|
||||
throw UnsupportedOperationException("Only classes are supported as parameters in script template: $classifier")
|
||||
|
||||
val type = getKotlinType(scriptDescriptor, classifier)
|
||||
val typeProjections = kType.arguments.map { getTypeProjection(scriptDescriptor, it) }
|
||||
val isNullable = kType.isMarkedNullable
|
||||
|
||||
return KotlinTypeFactory.simpleType(Annotations.EMPTY, type.constructor, typeProjections, isNullable)
|
||||
}
|
||||
|
||||
private fun getTypeProjection(scriptDescriptor: ScriptDescriptor, kTypeProjection: KTypeProjection): TypeProjection {
|
||||
val kType = kTypeProjection.type ?: throw UnsupportedOperationException("Star projections are not supported")
|
||||
|
||||
val type = getKotlinTypeByKType(scriptDescriptor, kType)
|
||||
|
||||
val variance = when (kTypeProjection.variance) {
|
||||
KVariance.IN -> Variance.IN_VARIANCE
|
||||
KVariance.OUT -> Variance.OUT_VARIANCE
|
||||
KVariance.INVARIANT -> Variance.INVARIANT
|
||||
null -> throw UnsupportedOperationException("Star projections are not supported")
|
||||
}
|
||||
|
||||
return TypeProjectionImpl(variance, type)
|
||||
}
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Copyright 2010-2016 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.script
|
||||
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.StandardFileSystems
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.PsiManager
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.parsing.KotlinParserDefinition
|
||||
import org.jetbrains.kotlin.psi.KtAnnotationEntry
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtScript
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||
import java.io.File
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.memberFunctions
|
||||
import kotlin.reflect.primaryConstructor
|
||||
|
||||
open class KotlinScriptDefinitionFromAnnotatedTemplate(
|
||||
template: KClass<out Any>,
|
||||
providedResolver: ScriptDependenciesResolver? = null,
|
||||
providedScriptFilePattern: String? = null,
|
||||
val environment: Map<String, Any?>? = null
|
||||
) : KotlinScriptDefinition(template) {
|
||||
|
||||
val scriptFilePattern by lazy {
|
||||
providedScriptFilePattern ?: template.annotations.firstIsInstanceOrNull<ScriptTemplateDefinition>()?.scriptFilePattern ?: DEFAULT_SCRIPT_FILE_PATTERN
|
||||
}
|
||||
|
||||
val resolver: ScriptDependenciesResolver? by lazy {
|
||||
val defAnn by lazy { template.annotations.firstIsInstanceOrNull<ScriptTemplateDefinition>() }
|
||||
when {
|
||||
providedResolver != null -> providedResolver
|
||||
// TODO: logScriptDefMessage missing or invalid constructor
|
||||
defAnn != null ->
|
||||
try {
|
||||
defAnn.resolver.primaryConstructor?.call() ?: null.apply {
|
||||
log.error("[kts] No default constructor found for ${defAnn.resolver.qualifiedName}")
|
||||
}
|
||||
}
|
||||
catch (ex: ClassCastException) {
|
||||
log.error("[kts] Script def error ${ex.message}")
|
||||
null
|
||||
}
|
||||
else -> BasicScriptDependenciesResolver()
|
||||
}
|
||||
}
|
||||
|
||||
private val acceptedAnnotations: List<KClass<out Annotation>> by lazy {
|
||||
val resolveMethod = ScriptDependenciesResolver::resolve
|
||||
val resolverMethodAnnotations =
|
||||
resolver::class.memberFunctions.find {
|
||||
it.name == resolveMethod.name &&
|
||||
sameSignature(it, resolveMethod)
|
||||
}
|
||||
?.annotations
|
||||
?.filterIsInstance<AcceptedAnnotations>()
|
||||
resolverMethodAnnotations?.flatMap {
|
||||
val v = it.supportedAnnotationClasses
|
||||
v.toList() // TODO: inline after KT-9453 is resolved (now it fails with "java.lang.Class cannot be cast to kotlin.reflect.KClass")
|
||||
}
|
||||
?: emptyList()
|
||||
}
|
||||
|
||||
override val name = template.simpleName!!
|
||||
|
||||
override fun <TF> isScript(file: TF): Boolean =
|
||||
scriptFilePattern.let { Regex(it).matches(getFileName(file)) }
|
||||
|
||||
// TODO: implement other strategy - e.g. try to extract something from match with ScriptFilePattern
|
||||
override fun getScriptName(script: KtScript): Name = ScriptNameUtil.fileNameWithExtensionStripped(script, KotlinParserDefinition.STD_SCRIPT_EXT)
|
||||
|
||||
override fun <TF> getDependenciesFor(file: TF, project: Project, previousDependencies: KotlinScriptExternalDependencies?): KotlinScriptExternalDependencies? {
|
||||
|
||||
val script = BasicScriptContents(file, getAnnotations = {
|
||||
val classLoader = (template as Any).javaClass.classLoader
|
||||
getAnnotationEntries(file, project)
|
||||
.mapNotNull { psiAnn ->
|
||||
// TODO: consider advanced matching using semantic similar to actual resolving
|
||||
acceptedAnnotations.find { ann ->
|
||||
psiAnn.typeName.let { it == ann.simpleName || it == ann.qualifiedName }
|
||||
}?.let { KtAnnotationWrapper(psiAnn, classLoader.loadClass(it.qualifiedName).kotlin as KClass<out Annotation>) }
|
||||
}
|
||||
.map { it.getProxy(classLoader) }
|
||||
})
|
||||
try {
|
||||
val fileDeps = resolver?.resolve(script, environment, ::logScriptDefMessage, previousDependencies)
|
||||
// TODO: use it as a Future
|
||||
return fileDeps?.get()
|
||||
}
|
||||
catch (ex: ClassCastException) {
|
||||
logScriptDefMessage(ScriptDependenciesResolver.ReportSeverity.ERROR, ex.message ?: "Invalid script template: ${template.qualifiedName}", null)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun <TF> getAnnotationEntries(file: TF, project: Project): Iterable<KtAnnotationEntry> = when (file) {
|
||||
is PsiFile -> getAnnotationEntriesFromPsiFile(file)
|
||||
is VirtualFile -> getAnnotationEntriesFromVirtualFile(file, project)
|
||||
is File -> {
|
||||
val virtualFile = (StandardFileSystems.local().findFileByPath(file.canonicalPath)
|
||||
?: throw IllegalArgumentException("Unable to find file ${file.canonicalPath}"))
|
||||
getAnnotationEntriesFromVirtualFile(virtualFile, project)
|
||||
}
|
||||
else -> throw IllegalArgumentException("Unsupported file type $file")
|
||||
}
|
||||
|
||||
private fun getAnnotationEntriesFromPsiFile(file: PsiFile) =
|
||||
if (file is KtFile) file.annotationEntries
|
||||
else throw IllegalArgumentException("Unable to extract kotlin annotations from ${file.name} (${file.fileType})")
|
||||
|
||||
private fun getAnnotationEntriesFromVirtualFile(file: VirtualFile, project: Project): Iterable<KtAnnotationEntry> {
|
||||
val psiFile: PsiFile = PsiManager.getInstance(project).findFile(file)
|
||||
?: throw IllegalArgumentException("Unable to load PSI from ${file.canonicalPath}")
|
||||
return getAnnotationEntriesFromPsiFile(psiFile)
|
||||
}
|
||||
|
||||
class BasicScriptContents<out TF>(myFile: TF, getAnnotations: () -> Iterable<Annotation>) : ScriptContents {
|
||||
override val file: File? by lazy { getFile(myFile) }
|
||||
override val annotations: Iterable<Annotation> by lazy { getAnnotations() }
|
||||
override val text: CharSequence? by lazy { getFileContents(myFile) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
internal val log = Logger.getInstance(KotlinScriptDefinitionFromAnnotatedTemplate::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun logScriptDefMessage(reportSeverity: ScriptDependenciesResolver.ReportSeverity, s: String, position: ScriptContents.Position?): Unit {
|
||||
val msg = (position?.run { "[at $line:$col]" } ?: "") + s
|
||||
when (reportSeverity) {
|
||||
ScriptDependenciesResolver.ReportSeverity.ERROR -> KotlinScriptDefinitionFromAnnotatedTemplate.log.error(msg)
|
||||
ScriptDependenciesResolver.ReportSeverity.WARNING -> KotlinScriptDefinitionFromAnnotatedTemplate.log.warn(msg)
|
||||
ScriptDependenciesResolver.ReportSeverity.INFO -> KotlinScriptDefinitionFromAnnotatedTemplate.log.info(msg)
|
||||
ScriptDependenciesResolver.ReportSeverity.DEBUG -> KotlinScriptDefinitionFromAnnotatedTemplate.log.debug(msg)
|
||||
}
|
||||
}
|
||||
@@ -72,3 +72,9 @@ class KotlinScriptDefinitionProvider {
|
||||
ServiceManager.getService(project, KotlinScriptDefinitionProvider::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
fun getScriptDefinition(file: VirtualFile, project: Project): KotlinScriptDefinition? =
|
||||
KotlinScriptDefinitionProvider.getInstance(project).findScriptDefinition(file)
|
||||
|
||||
fun getScriptDefinition(psiFile: PsiFile): KotlinScriptDefinition? =
|
||||
KotlinScriptDefinitionProvider.getInstance(psiFile.project).findScriptDefinition(psiFile)
|
||||
|
||||
+13
-11
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.script
|
||||
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 java.io.File
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||
import kotlin.concurrent.read
|
||||
@@ -40,19 +41,18 @@ class KotlinScriptExternalImportsProvider(val project: Project, private val scri
|
||||
val path = getFilePath(file)
|
||||
return cache[path]
|
||||
?: if (cacheOfNulls.contains(path)) null
|
||||
else scriptDefinitionProvider.findScriptDefinition(file)
|
||||
?.let { it.getDependenciesFor(file, project, null) }
|
||||
.apply {
|
||||
log.info("[kts] new cached deps for $path: ${this?.classpath?.joinToString(File.pathSeparator)}")
|
||||
cacheLock.write {
|
||||
if (this == null) {
|
||||
cacheOfNulls.add(path)
|
||||
}
|
||||
else {
|
||||
cache.put(path, this)
|
||||
else scriptDefinitionProvider.findScriptDefinition(file)?.getDependenciesFor(file, project, null)
|
||||
.apply {
|
||||
log.info("[kts] new cached deps for $path: ${this?.classpath?.joinToString(File.pathSeparator)}")
|
||||
cacheLock.write {
|
||||
if (this == null) {
|
||||
cacheOfNulls.add(path)
|
||||
}
|
||||
else {
|
||||
cache.put(path, this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// optimized for initial caching, additional handling of possible duplicates to save a call to distinct
|
||||
@@ -162,3 +162,5 @@ internal fun Iterable<File>.isSamePathListAs(other: Iterable<File>): Boolean =
|
||||
!(first.hasNext() || second.hasNext())
|
||||
}
|
||||
|
||||
fun getScriptExternalDependencies(file: VirtualFile, project: Project): KotlinScriptExternalDependencies? =
|
||||
KotlinScriptExternalImportsProvider.getInstance(project)?.getExternalImports(file)
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2016 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.script
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiFile
|
||||
|
||||
fun getScriptDefinition(file: VirtualFile, project: Project): KotlinScriptDefinition? =
|
||||
KotlinScriptDefinitionProvider.getInstance(project).findScriptDefinition(file)
|
||||
|
||||
fun getScriptDefinition(psiFile: PsiFile): KotlinScriptDefinition? =
|
||||
KotlinScriptDefinitionProvider.getInstance(psiFile.project).findScriptDefinition(psiFile)
|
||||
|
||||
fun getScriptExternalDependencies(file: VirtualFile, project: Project): KotlinScriptExternalDependencies? =
|
||||
KotlinScriptExternalImportsProvider.getInstance(project)?.getExternalImports(file)
|
||||
@@ -1,172 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2016 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.script
|
||||
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.StandardFileSystems
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.PsiManager
|
||||
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.parsing.KotlinParserDefinition
|
||||
import org.jetbrains.kotlin.psi.KtAnnotationEntry
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtScript
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||
import java.io.File
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.memberFunctions
|
||||
import kotlin.reflect.primaryConstructor
|
||||
|
||||
open class KotlinScriptDefinitionFromTemplate(final override val template: KClass<out Any>,
|
||||
val resolver: ScriptDependenciesResolver? = null,
|
||||
val scriptFilePattern: String? = null,
|
||||
val environment: Map<String, Any?>? = null
|
||||
) : KotlinScriptDefinition {
|
||||
|
||||
// TODO: remove this and simplify definitionAnnotation as soon as deprecated annotations will be removed
|
||||
internal class ScriptTemplateDefinitionData(val resolverClass: KClass<out ScriptDependenciesResolver>,
|
||||
makeResolver: () -> ScriptDependenciesResolver?,
|
||||
val scriptFilePattern: String?) {
|
||||
|
||||
val resolver: ScriptDependenciesResolver? by lazy(makeResolver)
|
||||
|
||||
val acceptedAnnotations: List<KClass<out Annotation>> by lazy {
|
||||
val resolveMethod = ScriptDependenciesResolver::resolve
|
||||
val resolverMethodAnnotations =
|
||||
resolverClass.memberFunctions.find {
|
||||
it.name == resolveMethod.name &&
|
||||
sameSignature(it, resolveMethod)
|
||||
}
|
||||
?.annotations
|
||||
?.filterIsInstance<AcceptedAnnotations>()
|
||||
resolverMethodAnnotations?.flatMap {
|
||||
val v = it.supportedAnnotationClasses
|
||||
v.toList() // TODO: inline after KT-9453 is resolved (now it fails with "java.lang.Class cannot be cast to kotlin.reflect.KClass")
|
||||
}
|
||||
?: emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
private val definitionData by lazy {
|
||||
val defAnn = template.annotations.firstIsInstanceOrNull<ScriptTemplateDefinition>()
|
||||
val filePattern = scriptFilePattern ?: defAnn?.scriptFilePattern ?: DEFAULT_SCRIPT_FILE_PATTERN
|
||||
when {
|
||||
resolver != null -> ScriptTemplateDefinitionData(resolver.javaClass.kotlin, { resolver }, filePattern)
|
||||
// TODO: logScriptDefMessage missing or invalid constructor
|
||||
defAnn != null -> ScriptTemplateDefinitionData(defAnn.resolver,
|
||||
{
|
||||
try {
|
||||
defAnn.resolver.primaryConstructor?.call() ?: null.apply {
|
||||
log.error("[kts] No default constructor found for ${defAnn.resolver.qualifiedName}")
|
||||
}
|
||||
}
|
||||
catch (ex: ClassCastException) {
|
||||
log.error("[kts] Script def error ${ex.message}")
|
||||
null
|
||||
}
|
||||
},
|
||||
filePattern)
|
||||
else -> ScriptTemplateDefinitionData(BasicScriptDependenciesResolver::class, ::BasicScriptDependenciesResolver, filePattern)
|
||||
}
|
||||
}
|
||||
|
||||
override val name = template.simpleName!!
|
||||
|
||||
override fun getScriptParameters(scriptDescriptor: ScriptDescriptor): List<ScriptParameter> =
|
||||
template.primaryConstructor!!.parameters.map { ScriptParameter(Name.identifier(it.name!!), getKotlinTypeByKType(scriptDescriptor, it.type)) }
|
||||
|
||||
override fun getScriptSupertypes(scriptDescriptor: ScriptDescriptor): List<KotlinType> =
|
||||
listOf(getKotlinTypeByFqName(scriptDescriptor, template.qualifiedName!!))
|
||||
|
||||
override fun getScriptParametersToPassToSuperclass(scriptDescriptor: ScriptDescriptor): List<Name> =
|
||||
getScriptParameters(scriptDescriptor).map { it.name }
|
||||
|
||||
override fun <TF> isScript(file: TF): Boolean =
|
||||
definitionData.scriptFilePattern?.let { Regex(it).matches(getFileName(file)) } ?: false
|
||||
|
||||
// TODO: implement other strategy - e.g. try to extract something from match with ScriptFilePattern
|
||||
override fun getScriptName(script: KtScript): Name = ScriptNameUtil.fileNameWithExtensionStripped(script, KotlinParserDefinition.STD_SCRIPT_EXT)
|
||||
|
||||
override fun <TF> getDependenciesFor(file: TF, project: Project, previousDependencies: KotlinScriptExternalDependencies?): KotlinScriptExternalDependencies? {
|
||||
|
||||
val script = BasicScriptContents(file, getAnnotations = {
|
||||
val classLoader = (template as Any).javaClass.classLoader
|
||||
getAnnotationEntries(file, project)
|
||||
.mapNotNull { psiAnn ->
|
||||
// TODO: consider advanced matching using semantic similar to actual resolving
|
||||
definitionData.acceptedAnnotations.find { ann ->
|
||||
psiAnn.typeName.let { it == ann.simpleName || it == ann.qualifiedName }
|
||||
}?.let { KtAnnotationWrapper(psiAnn, classLoader.loadClass(it.qualifiedName).kotlin as KClass<out Annotation>) }
|
||||
}
|
||||
.map { it.getProxy(classLoader) }
|
||||
})
|
||||
try {
|
||||
val fileDeps = definitionData.resolver?.resolve(script, environment, ::logScriptDefMessage, previousDependencies)
|
||||
// TODO: use it as a Future
|
||||
return fileDeps?.get()
|
||||
}
|
||||
catch (ex: ClassCastException) {
|
||||
logScriptDefMessage(ScriptDependenciesResolver.ReportSeverity.ERROR, ex.message ?: "Invalid script template: ${template.qualifiedName}", null)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun <TF> getAnnotationEntries(file: TF, project: Project): Iterable<KtAnnotationEntry> = when (file) {
|
||||
is PsiFile -> getAnnotationEntriesFromPsiFile(file)
|
||||
is VirtualFile -> getAnnotationEntriesFromVirtualFile(file, project)
|
||||
is File -> {
|
||||
val virtualFile = (StandardFileSystems.local().findFileByPath(file.canonicalPath)
|
||||
?: throw IllegalArgumentException("Unable to find file ${file.canonicalPath}"))
|
||||
getAnnotationEntriesFromVirtualFile(virtualFile, project)
|
||||
}
|
||||
else -> throw IllegalArgumentException("Unsupported file type $file")
|
||||
}
|
||||
|
||||
private fun getAnnotationEntriesFromPsiFile(file: PsiFile) =
|
||||
if (file is KtFile) file.annotationEntries
|
||||
else throw IllegalArgumentException("Unable to extract kotlin annotations from ${file.name} (${file.fileType})")
|
||||
|
||||
private fun getAnnotationEntriesFromVirtualFile(file: VirtualFile, project: Project): Iterable<KtAnnotationEntry> {
|
||||
val psiFile: PsiFile = PsiManager.getInstance(project).findFile(file)
|
||||
?: throw IllegalArgumentException("Unable to load PSI from ${file.canonicalPath}")
|
||||
return getAnnotationEntriesFromPsiFile(psiFile)
|
||||
}
|
||||
|
||||
class BasicScriptContents<out TF>(myFile: TF, getAnnotations: () -> Iterable<Annotation>) : ScriptContents {
|
||||
override val file: File? by lazy { getFile(myFile) }
|
||||
override val annotations: Iterable<Annotation> by lazy { getAnnotations() }
|
||||
override val text: CharSequence? by lazy { getFileContents(myFile) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
internal val log = Logger.getInstance(KotlinScriptDefinitionFromTemplate::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun logScriptDefMessage(reportSeverity: ScriptDependenciesResolver.ReportSeverity, s: String, position: ScriptContents.Position?): Unit {
|
||||
val msg = (position?.run { "[at $line:$col]" } ?: "") + s
|
||||
when (reportSeverity) {
|
||||
ScriptDependenciesResolver.ReportSeverity.ERROR -> KotlinScriptDefinitionFromTemplate.log.error(msg)
|
||||
ScriptDependenciesResolver.ReportSeverity.WARNING -> KotlinScriptDefinitionFromTemplate.log.warn(msg)
|
||||
ScriptDependenciesResolver.ReportSeverity.INFO -> KotlinScriptDefinitionFromTemplate.log.info(msg)
|
||||
ScriptDependenciesResolver.ReportSeverity.DEBUG -> KotlinScriptDefinitionFromTemplate.log.debug(msg)
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -48,13 +48,13 @@ interface ScriptTemplatesProvider {
|
||||
|
||||
fun makeScriptDefsFromTemplatesProviderExtensions(project: Project,
|
||||
errorsHandler: ((ScriptTemplatesProvider, Exception) -> Unit) = { ep, ex -> throw ex }
|
||||
): List<KotlinScriptDefinitionFromTemplate> =
|
||||
): List<KotlinScriptDefinitionFromAnnotatedTemplate> =
|
||||
makeScriptDefsFromTemplatesProviders(Extensions.getArea(project).getExtensionPoint(ScriptTemplatesProvider.EP_NAME).extensions.asIterable(),
|
||||
errorsHandler)
|
||||
|
||||
fun makeScriptDefsFromTemplatesProviders(providers: Iterable<ScriptTemplatesProvider>,
|
||||
errorsHandler: ((ScriptTemplatesProvider, Exception) -> Unit) = { ep, ex -> throw ex }
|
||||
): List<KotlinScriptDefinitionFromTemplate> {
|
||||
): List<KotlinScriptDefinitionFromAnnotatedTemplate> {
|
||||
val idToVersion = hashMapOf<String, Int>()
|
||||
return providers.filter { it.isValid }.sortedByDescending { it.version }.flatMap { provider ->
|
||||
try {
|
||||
@@ -65,12 +65,12 @@ fun makeScriptDefsFromTemplatesProviders(providers: Iterable<ScriptTemplatesProv
|
||||
provider.templateClassNames.map {
|
||||
val cl = loader.loadClass(it)
|
||||
idToVersion.put(provider.id, provider.version)
|
||||
KotlinScriptDefinitionFromTemplate(cl.kotlin, provider.resolver, provider.filePattern, provider.environment)
|
||||
KotlinScriptDefinitionFromAnnotatedTemplate(cl.kotlin, provider.resolver, provider.filePattern, provider.environment)
|
||||
}
|
||||
}
|
||||
catch (ex: Exception) {
|
||||
errorsHandler(provider, ex)
|
||||
emptyList<KotlinScriptDefinitionFromTemplate>()
|
||||
emptyList<KotlinScriptDefinitionFromAnnotatedTemplate>()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2010-2016 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.script
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.annotations.Annotations
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.module
|
||||
import org.jetbrains.kotlin.serialization.deserialization.NotFoundClasses
|
||||
import org.jetbrains.kotlin.serialization.deserialization.findNonGenericClassAcrossDependencies
|
||||
import org.jetbrains.kotlin.storage.LockBasedStorageManager
|
||||
import org.jetbrains.kotlin.types.*
|
||||
import kotlin.reflect.*
|
||||
|
||||
data class ScriptParameter(val name: Name, val type: KotlinType)
|
||||
|
||||
fun KotlinScriptDefinition.getScriptParameters(scriptDescriptor: ScriptDescriptor): List<ScriptParameter> =
|
||||
template.primaryConstructor?.parameters
|
||||
?.map { ScriptParameter(Name.identifier(it.name!!), getKotlinTypeByKType(scriptDescriptor, it.type)) }
|
||||
?: emptyList()
|
||||
|
||||
fun getKotlinType(scriptDescriptor: ScriptDescriptor, kClass: KClass<out Any>): KotlinType =
|
||||
getKotlinTypeByFqName(scriptDescriptor,
|
||||
kClass.qualifiedName ?: throw java.lang.RuntimeException("Cannot get FQN from $kClass"))
|
||||
|
||||
fun getKotlinTypeByFqName(scriptDescriptor: ScriptDescriptor, fqName: String): KotlinType =
|
||||
scriptDescriptor.module.findNonGenericClassAcrossDependencies(
|
||||
ClassId.topLevel(FqName(fqName)),
|
||||
NotFoundClasses(LockBasedStorageManager.NO_LOCKS, scriptDescriptor.module)
|
||||
).defaultType
|
||||
|
||||
// TODO: support star projections
|
||||
// TODO: support annotations on types and type parameters
|
||||
// TODO: support type parameters on types and type projections
|
||||
fun getKotlinTypeByKType(scriptDescriptor: ScriptDescriptor, kType: KType): KotlinType {
|
||||
val classifier = kType.classifier
|
||||
if (classifier !is KClass<*>)
|
||||
throw java.lang.UnsupportedOperationException("Only classes are supported as parameters in script template: $classifier")
|
||||
|
||||
val type = getKotlinType(scriptDescriptor, classifier)
|
||||
val typeProjections = kType.arguments.map { getTypeProjection(scriptDescriptor, it) }
|
||||
val isNullable = kType.isMarkedNullable
|
||||
|
||||
return KotlinTypeFactory.simpleType(Annotations.EMPTY, type.constructor, typeProjections, isNullable)
|
||||
}
|
||||
|
||||
private fun getTypeProjection(scriptDescriptor: ScriptDescriptor, kTypeProjection: KTypeProjection): TypeProjection {
|
||||
val kType = kTypeProjection.type ?: throw java.lang.UnsupportedOperationException("Star projections are not supported")
|
||||
|
||||
val type = getKotlinTypeByKType(scriptDescriptor, kType)
|
||||
|
||||
val variance = when (kTypeProjection.variance) {
|
||||
KVariance.IN -> Variance.IN_VARIANCE
|
||||
KVariance.OUT -> Variance.OUT_VARIANCE
|
||||
KVariance.INVARIANT -> Variance.INVARIANT
|
||||
null -> throw java.lang.UnsupportedOperationException("Star projections are not supported")
|
||||
}
|
||||
|
||||
return TypeProjectionImpl(variance, type)
|
||||
}
|
||||
Reference in New Issue
Block a user