Refactor script dependencies management
This commit is contained in:
committed by
Pavel V. Talanov
parent
c244414f3c
commit
082290f8e3
@@ -34,7 +34,7 @@ import org.jetbrains.kotlin.resolve.scopes.ImportingScope
|
||||
import org.jetbrains.kotlin.resolve.scopes.LexicalScope
|
||||
import org.jetbrains.kotlin.resolve.scopes.SubpackagesImportingScope
|
||||
import org.jetbrains.kotlin.resolve.source.KotlinSourceElement
|
||||
import org.jetbrains.kotlin.script.getScriptExtraImports
|
||||
import org.jetbrains.kotlin.script.getScriptExternalDependencies
|
||||
import org.jetbrains.kotlin.storage.StorageManager
|
||||
import org.jetbrains.kotlin.storage.getValue
|
||||
import org.jetbrains.kotlin.types.TypeSubstitutor
|
||||
@@ -73,7 +73,7 @@ class FileScopeFactory(
|
||||
val explicitImportResolver = createImportResolver(ExplicitImportsIndexed(imports), bindingTrace)
|
||||
val allUnderImportResolver = createImportResolver(AllUnderImportsIndexed(imports), bindingTrace)
|
||||
|
||||
val extraImports = ktImportsFactory.createImportDirectives(getScriptExtraImports(file).flatMap { it.names.map { ImportPath(it) } })
|
||||
val extraImports = ktImportsFactory.createImportDirectives(getScriptExternalDependencies(file).flatMap { it.imports.map { ImportPath(it) } })
|
||||
val allImplicitImports = defaultImports + extraImports
|
||||
|
||||
val defaultImportsFiltered = if (aliasImportNames.isEmpty()) { // optimization
|
||||
|
||||
+19
-10
@@ -17,14 +17,6 @@
|
||||
package org.jetbrains.kotlin.script
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.util.xmlb.XmlSerializer
|
||||
import com.intellij.util.xmlb.annotations.AbstractCollection
|
||||
import com.intellij.util.xmlb.annotations.Tag
|
||||
import org.jdom.Document
|
||||
import org.jdom.Element
|
||||
import org.jdom.output.Format
|
||||
import org.jdom.output.XMLOutputter
|
||||
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.parsing.KotlinParserDefinition
|
||||
@@ -54,7 +46,24 @@ data class KotlinConfigurableScriptDefinition(val config: KotlinScriptConfig, va
|
||||
|
||||
private val evaluatedClasspath by lazy { config.classpath.evalWithVars(environmentVars).distinct() }
|
||||
|
||||
override fun getScriptDependenciesClasspath(): List<String> = evaluatedClasspath
|
||||
override fun <TF> getDependenciesFor(file: TF, project: Project): KotlinScriptExternalDependencies? =
|
||||
if (!isScript(file)) null
|
||||
else {
|
||||
val extDeps = getScriptDependenciesFromConfig(file)
|
||||
when {
|
||||
extDeps != null ->
|
||||
object : KotlinScriptExternalDependencies {
|
||||
override val classpath = evaluatedClasspath + extDeps.classpath.evalWithVars(environmentVars).distinct()
|
||||
override val imports = extDeps.imports
|
||||
override val sources = extDeps.sources.evalWithVars(environmentVars).distinct()
|
||||
}
|
||||
!evaluatedClasspath.isEmpty() ->
|
||||
object : KotlinScriptExternalDependencies {
|
||||
override val classpath = evaluatedClasspath
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -62,7 +71,7 @@ data class KotlinConfigurableScriptDefinition(val config: KotlinScriptConfig, va
|
||||
// if corresponding list of replacements is empty, all strings containing the reference to the var are removed
|
||||
// TODO: fix and tests
|
||||
// TODO: move to some utils
|
||||
internal fun List<String>.evalWithVars(varsMap: Map<String, List<String>>?): List<String> =
|
||||
internal fun Iterable<String>.evalWithVars(varsMap: Map<String, List<String>>?): Iterable<String> =
|
||||
if (varsMap == null || varsMap.isEmpty()) this
|
||||
else this.flatMap { cpentry ->
|
||||
varsMap.entries.fold(listOf(cpentry)) { p, v ->
|
||||
|
||||
@@ -16,10 +16,12 @@
|
||||
|
||||
package org.jetbrains.kotlin.script
|
||||
|
||||
import com.intellij.openapi.fileTypes.LanguageFileType
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.util.PathUtil
|
||||
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
@@ -36,19 +38,39 @@ import java.io.File
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
interface KotlinScriptDefinition {
|
||||
val name: String
|
||||
val name: String get() = "Kotlin Script"
|
||||
|
||||
// TODO: consider creating separate type (subtype? for kotlin scripts)
|
||||
val fileType: LanguageFileType get() = KotlinFileType.INSTANCE
|
||||
|
||||
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 <TF> isScript(file: TF): Boolean
|
||||
fun getScriptName(script: KtScript): Name
|
||||
fun getScriptDependenciesClasspath(): List<String> = emptyList()
|
||||
|
||||
fun getScriptName(script: KtScript): Name =
|
||||
ScriptNameUtil.fileNameWithExtensionStripped(script, KotlinParserDefinition.STD_SCRIPT_EXT)
|
||||
|
||||
fun <TF> getDependenciesFor(file: TF, project: Project): KotlinScriptExternalDependencies? = null
|
||||
}
|
||||
|
||||
interface KotlinScriptExternalDependencies {
|
||||
val classpath: Iterable<String> get() = emptyList()
|
||||
val imports: Iterable<String> get() = emptyList()
|
||||
val sources: Iterable<String> get() = emptyList()
|
||||
}
|
||||
|
||||
class KotlinScriptExternalDependenciesUnion(val dependencies: Iterable<KotlinScriptExternalDependencies>) : KotlinScriptExternalDependencies {
|
||||
override val classpath: Iterable<String> get() = dependencies.flatMap { it.classpath }
|
||||
override val imports: Iterable<String> get() = dependencies.flatMap { it.imports }
|
||||
override val sources: Iterable<String> get() = dependencies.flatMap { it.sources }
|
||||
}
|
||||
|
||||
data class ScriptParameter(val name: Name, val type: KotlinType)
|
||||
|
||||
fun <TF> getFileExtension(file: TF) = PathUtil.getFileExtension(getFileName(file))
|
||||
|
||||
fun <TF> getFileName(file: TF): String = when (file) {
|
||||
is PsiFile -> file.originalFile.name
|
||||
is VirtualFile -> file.name
|
||||
@@ -56,17 +78,16 @@ fun <TF> getFileName(file: TF): String = when (file) {
|
||||
else -> throw IllegalArgumentException("Unsupported file type $file")
|
||||
}
|
||||
|
||||
fun <TF> getFilePath(file: TF): String = when (file) {
|
||||
is PsiFile -> file.originalFile.run { virtualFile?.path ?: name } // TODO: replace name with path of PSI elements
|
||||
is VirtualFile -> file.path
|
||||
is File -> file.canonicalPath
|
||||
else -> throw IllegalArgumentException("Unsupported file type $file")
|
||||
}
|
||||
|
||||
object StandardScriptDefinition : KotlinScriptDefinition {
|
||||
private val ARGS_NAME = Name.identifier("args")
|
||||
|
||||
override val name = "Kotlin Script"
|
||||
|
||||
override fun getScriptName(script: KtScript): Name =
|
||||
ScriptNameUtil.fileNameWithExtensionStripped(script, KotlinParserDefinition.STD_SCRIPT_EXT)
|
||||
|
||||
override fun <TF> isScript(file: TF): Boolean =
|
||||
getFileExtension(file) == KotlinParserDefinition.STD_SCRIPT_SUFFIX
|
||||
|
||||
// NOTE: for now we treat .kts files as if they have 'args: Array<String>' parameter
|
||||
// this is not supposed to be final design
|
||||
override fun getScriptParameters(scriptDescriptor: ScriptDescriptor): List<ScriptParameter> =
|
||||
|
||||
+7
-12
@@ -17,6 +17,7 @@
|
||||
package org.jetbrains.kotlin.script
|
||||
|
||||
import com.intellij.openapi.components.ServiceManager
|
||||
import com.intellij.openapi.fileTypes.LanguageFileType
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiFile
|
||||
@@ -28,10 +29,8 @@ class KotlinScriptDefinitionProvider {
|
||||
|
||||
private val definitions: MutableList<KotlinScriptDefinition> = arrayListOf(StandardScriptDefinition)
|
||||
private val definitionsLock = java.util.concurrent.locks.ReentrantReadWriteLock()
|
||||
private val notificationHandlers = ArrayList<() -> Unit>()
|
||||
private val handlersLock = java.util.concurrent.locks.ReentrantReadWriteLock()
|
||||
|
||||
fun setScriptDefinitions(newDefinitions: List<KotlinScriptDefinition>): Unit {
|
||||
fun setScriptDefinitions(newDefinitions: List<KotlinScriptDefinition>): Boolean {
|
||||
var changed = false
|
||||
definitionsLock.read {
|
||||
if (newDefinitions != definitions) {
|
||||
@@ -42,11 +41,7 @@ class KotlinScriptDefinitionProvider {
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
handlersLock.read {
|
||||
notificationHandlers.forEach { it() }
|
||||
}
|
||||
}
|
||||
return changed
|
||||
}
|
||||
|
||||
fun<TF> findScriptDefinition(file: TF): KotlinScriptDefinition? = definitionsLock.read {
|
||||
@@ -55,10 +50,6 @@ class KotlinScriptDefinitionProvider {
|
||||
|
||||
fun<TF> isScript(file: TF): Boolean = findScriptDefinition(file) != null
|
||||
|
||||
fun subscribeOnDefinitionsChanged(handler: () -> Unit): Unit {
|
||||
handlersLock.write { notificationHandlers.add(handler) }
|
||||
}
|
||||
|
||||
fun addScriptDefinition(scriptDefinition: KotlinScriptDefinition) {
|
||||
definitionsLock.write {
|
||||
definitions.add(0, scriptDefinition)
|
||||
@@ -71,6 +62,10 @@ class KotlinScriptDefinitionProvider {
|
||||
}
|
||||
}
|
||||
|
||||
fun getAllKnownFileTypes(): Iterable<LanguageFileType> = definitionsLock.read {
|
||||
definitions.map { it.fileType }.distinct()
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun getInstance(project: Project): KotlinScriptDefinitionProvider =
|
||||
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* 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.components.ServiceManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||
import kotlin.concurrent.read
|
||||
import kotlin.concurrent.write
|
||||
|
||||
class KotlinScriptExternalImportsProvider(val project: Project, private val scriptDefinitionProvider: KotlinScriptDefinitionProvider) {
|
||||
private val cacheLock = ReentrantReadWriteLock()
|
||||
private val cache = hashMapOf<String, KotlinScriptExternalDependencies>()
|
||||
private val cacheOfNulls = hashSetOf<String>()
|
||||
|
||||
fun <TF> getExternalImports(vararg files: TF): List<KotlinScriptExternalDependencies> = getExternalImports(files.asIterable())
|
||||
|
||||
fun <TF> getExternalImports(files: Iterable<TF>): List<KotlinScriptExternalDependencies> = cacheLock.read {
|
||||
files.mapNotNull { file ->
|
||||
val path = getFilePath(file)
|
||||
cache[path]
|
||||
?: if (cacheOfNulls.contains(path)) null
|
||||
else scriptDefinitionProvider.findScriptDefinition(file)
|
||||
?.let { it.getDependenciesFor(file, project) }
|
||||
.apply { cacheLock.write {
|
||||
if (this == null) {
|
||||
cacheOfNulls.add(path)
|
||||
}
|
||||
else {
|
||||
cache.put(path, this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <TF> cacheExternalImports(files: Iterable<TF>): Unit = cacheLock.write {
|
||||
val uncached = hashSetOf<String>()
|
||||
files.forEach { file ->
|
||||
val path = getFilePath(file)
|
||||
if (!cache.containsKey(path) && !cacheOfNulls.contains(path) && !uncached.contains(path)) {
|
||||
val scriptDef = scriptDefinitionProvider.findScriptDefinition(file)
|
||||
if (scriptDef != null) {
|
||||
val deps = scriptDef.getDependenciesFor(file, project)
|
||||
if (deps != null) {
|
||||
cache.put(path, deps)
|
||||
}
|
||||
else {
|
||||
cacheOfNulls.add(path)
|
||||
}
|
||||
}
|
||||
else {
|
||||
uncached.add(path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun invalidateCaches() {
|
||||
cacheLock.write {
|
||||
cache.keys.toList().apply {
|
||||
cache.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <TF> invalidateCachesFor(vararg files: TF) { invalidateCachesFor(files.asIterable()) }
|
||||
|
||||
fun <TF> invalidateCachesFor(files: Iterable<TF>) {
|
||||
cacheLock.write {
|
||||
files.forEach { file ->
|
||||
val path = getFilePath(file)
|
||||
cache.remove(path)
|
||||
cacheOfNulls.remove(path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getKnownCombinedClasspath(): List<String> = cacheLock.read {
|
||||
cache.values.flatMap { it.classpath }
|
||||
}.distinct()
|
||||
|
||||
fun <TF> getCombinedClasspathFor(files: Iterable<TF>): List<String> =
|
||||
getExternalImports(files)
|
||||
.flatMap { it.classpath }
|
||||
.distinct()
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun getInstance(project: Project): KotlinScriptExternalImportsProvider? =
|
||||
ServiceManager.getService(project, KotlinScriptExternalImportsProvider::class.java)
|
||||
}
|
||||
}
|
||||
-132
@@ -1,132 +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.components.ServiceManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.*
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||
import kotlin.concurrent.read
|
||||
import kotlin.concurrent.write
|
||||
|
||||
// TODO: replace VirtualFile usage with File for performance/unnecessary dependencies reasons
|
||||
|
||||
class KotlinScriptExtraImportsProvider(val project: Project, private val scriptDefinitionProvider: KotlinScriptDefinitionProvider) {
|
||||
private val cacheLock = ReentrantReadWriteLock()
|
||||
private val preconfigured = hashMapOf<String, List<KotlinScriptExtraImport>>()
|
||||
private val cache = hashMapOf<String, List<KotlinScriptExtraImport>>()
|
||||
private val envVars: Map<String, List<String>> by lazy { generateKotlinScriptClasspathEnvVars(project) }
|
||||
private val notificationHandlers = ArrayList<(Iterable<String>) -> Unit>()
|
||||
private val handlersLock = java.util.concurrent.locks.ReentrantReadWriteLock()
|
||||
|
||||
init {
|
||||
val weakThis = WeakReference(this)
|
||||
scriptDefinitionProvider.subscribeOnDefinitionsChanged { weakThis.get()?.invalidateAllExtraImports() }
|
||||
}
|
||||
|
||||
fun isExtraImportsConfig(file: VirtualFile): Boolean = file.name.endsWith(IMPORTS_FILE_EXTENSION)
|
||||
|
||||
fun getExtraImports(vararg files: VirtualFile): List<KotlinScriptExtraImport> = getExtraImports(files.asIterable())
|
||||
|
||||
fun getExtraImports(files: Iterable<VirtualFile>): List<KotlinScriptExtraImport> {
|
||||
val newCashedFiles = ArrayList<String>()
|
||||
val res = cacheLock.read {
|
||||
files.flatMap { file ->
|
||||
if (file.isValid && !file.isDirectory) {
|
||||
preconfigured[file.path]
|
||||
?: cache[file.path]
|
||||
?: scriptDefinitionProvider.findScriptDefinition(file)?.let { def ->
|
||||
(listOf(KotlinScriptExtraImportFromDefinition(def)) +
|
||||
(file.parent.findFileByRelativePath(file.name + IMPORTS_FILE_EXTENSION)?.let {
|
||||
loadScriptExtraImportConfigs(it.inputStream).map { KotlinScriptExtraImportFromConfig(it, envVars) }
|
||||
} ?: emptyList()))
|
||||
.apply {
|
||||
cacheLock.write { cache.put(file.path, this) }
|
||||
newCashedFiles.add(file.path)
|
||||
}
|
||||
}
|
||||
?: emptyList()
|
||||
}
|
||||
else emptyList()
|
||||
}
|
||||
}
|
||||
notifyIfAny(newCashedFiles)
|
||||
return res
|
||||
}
|
||||
|
||||
fun preconfigureExtraImports(file: VirtualFile, extraImports: List<KotlinScriptExtraImport>?) {
|
||||
cacheLock.write {
|
||||
if (extraImports != null && extraImports.isNotEmpty()) {
|
||||
preconfigured[file.path] = extraImports
|
||||
}
|
||||
else {
|
||||
preconfigured.remove(file.path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun invalidateExtraImportsByImportsFiles(importsFiles: Iterable<VirtualFile>) {
|
||||
importsFiles.mapNotNull { it.parent.findFileByRelativePath(it.name.removeSuffix(IMPORTS_FILE_EXTENSION))?.path?.let { file ->
|
||||
cacheLock.write {
|
||||
cache.remove(it.path)?.let { file }
|
||||
}
|
||||
} }.let {
|
||||
notifyIfAny(it)
|
||||
}
|
||||
}
|
||||
|
||||
fun invalidateAllExtraImports() {
|
||||
cacheLock.write {
|
||||
cache.keys.toList().apply {
|
||||
cache.clear()
|
||||
}
|
||||
}.let {
|
||||
notifyIfAny(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun notifyIfAny(files: Iterable<String>) {
|
||||
if (files.any()) {
|
||||
handlersLock.read {
|
||||
notificationHandlers.forEach { it(files) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getKnownCombinedClasspath(): List<String> = cacheLock.read {
|
||||
cache.values.flatMap { it.flatMap { it.classpath } }
|
||||
}.distinct()
|
||||
|
||||
fun getCombinedClasspathFor(files: Iterable<VirtualFile>): List<String> =
|
||||
getExtraImports(files)
|
||||
.flatMap { it.classpath }
|
||||
.distinct()
|
||||
|
||||
fun subscribeOnExtraImportsChanged(handler: (Iterable<String>) -> Unit): Unit {
|
||||
handlersLock.write { notificationHandlers.add(handler) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun getInstance(project: Project): KotlinScriptExtraImportsProvider? =
|
||||
ServiceManager.getService(project, KotlinScriptExtraImportsProvider::class.java)
|
||||
|
||||
val IMPORTS_FILE_EXTENSION = ".ktsimports.xml"
|
||||
}
|
||||
}
|
||||
@@ -26,11 +26,11 @@ fun getScriptDefinition(file: VirtualFile, project: Project): KotlinScriptDefini
|
||||
fun getScriptDefinition(psiFile: PsiFile): KotlinScriptDefinition? =
|
||||
KotlinScriptDefinitionProvider.getInstance(psiFile.project).findScriptDefinition(psiFile)
|
||||
|
||||
fun getScriptExtraImports(file: VirtualFile, project: Project): List<KotlinScriptExtraImport> =
|
||||
KotlinScriptExtraImportsProvider.getInstance(project)?.getExtraImports(file) ?: emptyList()
|
||||
fun getScriptExternalDependencies(file: VirtualFile, project: Project): List<KotlinScriptExternalDependencies> =
|
||||
KotlinScriptExternalImportsProvider.getInstance(project)?.getExternalImports(file) ?: emptyList()
|
||||
|
||||
fun getScriptExtraImports(psiFile: PsiFile): List<KotlinScriptExtraImport> =
|
||||
fun getScriptExternalDependencies(psiFile: PsiFile): List<KotlinScriptExternalDependencies> =
|
||||
psiFile.virtualFile?.let { file ->
|
||||
KotlinScriptExtraImportsProvider.getInstance(psiFile.project)?.getExtraImports(file)
|
||||
KotlinScriptExternalImportsProvider.getInstance(psiFile.project)?.getExternalImports(file)
|
||||
} ?: emptyList()
|
||||
|
||||
|
||||
@@ -52,7 +52,8 @@ object SimpleUntypedAst {
|
||||
convert(expression.entries[0])
|
||||
else
|
||||
SimpleUntypedAst.Node.str(name, "")
|
||||
// convertStringTemplateExpression(expression, parent, expression.entries.size - 1)
|
||||
// TODO: parse expressions, etc. e.g.:
|
||||
// convertStringTemplateExpression(expression, parent, expression.entries.size - 1)
|
||||
}
|
||||
else -> Node.empty(name)
|
||||
|
||||
@@ -72,3 +73,9 @@ object SimpleUntypedAst {
|
||||
}
|
||||
}
|
||||
|
||||
fun parseAnnotation(ann: KtAnnotationEntry): SimpleUntypedAst.Node.list<Any> {
|
||||
val wann = SimpleUntypedAst.KtAnnotationWrapper(ann)
|
||||
val vals = wann.valueArguments
|
||||
return SimpleUntypedAst.Node.list(wann.name, vals)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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.util.JDOMUtil
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.util.xmlb.XmlSerializer
|
||||
import com.intellij.util.xmlb.annotations.AbstractCollection
|
||||
import com.intellij.util.xmlb.annotations.Tag
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.util.*
|
||||
|
||||
@Tag("import")
|
||||
class KotlinScriptExternalDependenciesConfig : KotlinScriptExternalDependencies {
|
||||
@Tag("classpath")
|
||||
@AbstractCollection(surroundWithTag = false, elementTag = "path", elementValueAttribute = "")
|
||||
override var classpath: MutableList<String> = ArrayList()
|
||||
|
||||
@Tag("imports")
|
||||
@AbstractCollection(surroundWithTag = false, elementTag = "name", elementValueAttribute = "")
|
||||
override var imports: MutableList<String> = ArrayList()
|
||||
|
||||
@Tag("sources")
|
||||
@AbstractCollection(surroundWithTag = false, elementTag = "path", elementValueAttribute = "")
|
||||
override var sources: MutableList<String> = ArrayList()
|
||||
}
|
||||
|
||||
fun loadScriptExternalImportConfigs(configFile: File): List<KotlinScriptExternalDependenciesConfig> =
|
||||
JDOMUtil.loadDocument(configFile).rootElement.children.mapNotNull {
|
||||
XmlSerializer.deserialize(it, KotlinScriptExternalDependenciesConfig::class.java)
|
||||
}
|
||||
|
||||
fun loadScriptExternalImportConfigs(configStream: InputStream): List<KotlinScriptExternalDependenciesConfig> =
|
||||
JDOMUtil.loadDocument(configStream).rootElement.children.mapNotNull {
|
||||
XmlSerializer.deserialize(it, KotlinScriptExternalDependenciesConfig::class.java)
|
||||
}
|
||||
|
||||
fun <TF> getScriptDependenciesFromConfig(file: TF): KotlinScriptExternalDependencies? {
|
||||
val IMPORTS_FILE_EXTENSION = ".ktsimports.xml"
|
||||
fun streamFromSibling(file: VirtualFile): InputStream? =
|
||||
file.parent.findFileByRelativePath(file.name + IMPORTS_FILE_EXTENSION)?.let { it.inputStream }
|
||||
fun streamFromSibling(file: File): InputStream? {
|
||||
val sibling = File(file.parentFile, file.name + IMPORTS_FILE_EXTENSION)
|
||||
return if (sibling.exists()) sibling.inputStream()
|
||||
else null
|
||||
}
|
||||
return when (file) {
|
||||
is VirtualFile -> streamFromSibling(file)
|
||||
is PsiFile -> streamFromSibling(file.originalFile.virtualFile)
|
||||
is File -> streamFromSibling(file)
|
||||
else -> throw IllegalArgumentException("Unsupported file type $file")
|
||||
}?.let { KotlinScriptExternalDependenciesUnion(loadScriptExternalImportConfigs(it)) }
|
||||
}
|
||||
@@ -1,67 +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.util.JDOMUtil
|
||||
import com.intellij.util.xmlb.XmlSerializer
|
||||
import com.intellij.util.xmlb.annotations.AbstractCollection
|
||||
import com.intellij.util.xmlb.annotations.Tag
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* A particular script dependency, additional to script kind dependencies defined by KotlinScriptDefinition
|
||||
*/
|
||||
interface KotlinScriptExtraImport {
|
||||
val classpath: List<String>
|
||||
val names: List<String>
|
||||
}
|
||||
|
||||
// -----
|
||||
|
||||
@Tag("import")
|
||||
class KotlinScriptExtraImportConfig {
|
||||
@Tag("classpath")
|
||||
@AbstractCollection(surroundWithTag = false, elementTag = "path", elementValueAttribute = "")
|
||||
var classpath: MutableList<String> = ArrayList()
|
||||
|
||||
@Tag("names")
|
||||
@AbstractCollection(surroundWithTag = false, elementTag = "name", elementValueAttribute = "")
|
||||
var names: MutableList<String> = ArrayList()
|
||||
}
|
||||
|
||||
fun loadScriptExtraImportConfigs(configFile: File): List<KotlinScriptExtraImportConfig> =
|
||||
JDOMUtil.loadDocument(configFile).rootElement.children.mapNotNull {
|
||||
XmlSerializer.deserialize(it, KotlinScriptExtraImportConfig::class.java)
|
||||
}
|
||||
|
||||
fun loadScriptExtraImportConfigs(configStream: InputStream): List<KotlinScriptExtraImportConfig> =
|
||||
JDOMUtil.loadDocument(configStream).rootElement.children.mapNotNull {
|
||||
XmlSerializer.deserialize(it, KotlinScriptExtraImportConfig::class.java)
|
||||
}
|
||||
|
||||
class KotlinScriptExtraImportFromConfig(val config : KotlinScriptExtraImportConfig, val envVars: Map<String, List<String>>) : KotlinScriptExtraImport {
|
||||
override val classpath: List<String> by lazy { config.classpath.evalWithVars(envVars).distinct() }
|
||||
override val names: List<String>
|
||||
get() = config.names
|
||||
}
|
||||
|
||||
class KotlinScriptExtraImportFromDefinition(val scriptDefinition: KotlinScriptDefinition) : KotlinScriptExtraImport {
|
||||
override val classpath: List<String> get() = scriptDefinition.getScriptDependenciesClasspath()
|
||||
override val names: List<String> = emptyList()
|
||||
}
|
||||
@@ -16,33 +16,33 @@
|
||||
|
||||
package org.jetbrains.kotlin.script
|
||||
|
||||
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.KtAnnotation
|
||||
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 java.io.File
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class ScriptFilePattern(val pattern: String)
|
||||
|
||||
interface ScriptDependencies {
|
||||
val classpath: List<String>
|
||||
val implicitImports: List<String>
|
||||
}
|
||||
|
||||
interface GetScriptDependencies {
|
||||
operator fun invoke(annotations: Iterable<KtAnnotationEntry>, context: Any?): ScriptDependencies? = null
|
||||
operator fun invoke(context: Any?): ScriptDependencies? = null
|
||||
operator fun invoke(annotations: Iterable<KtAnnotationEntry>, context: Any?): KotlinScriptExternalDependencies? = null
|
||||
operator fun invoke(context: Any?): KotlinScriptExternalDependencies? = null
|
||||
}
|
||||
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class ScriptDependencyExtractor(val extractor: KClass<out GetScriptDependencies>)
|
||||
annotation class ScriptDependencyResolver(val extractor: KClass<out GetScriptDependencies>)
|
||||
|
||||
data class KotlinScriptDefinitionFromTemplate(val template: KClass<out Any>, val context: Any?) : KotlinScriptDefinition {
|
||||
override val name = template.simpleName!!
|
||||
@@ -63,13 +63,39 @@ data class KotlinScriptDefinitionFromTemplate(val template: KClass<out Any>, val
|
||||
override fun getScriptName(script: KtScript): Name = ScriptNameUtil.fileNameWithExtensionStripped(script, KotlinParserDefinition.STD_SCRIPT_EXT)
|
||||
|
||||
private val dependenciesExtractors by lazy {
|
||||
template.annotations.mapNotNull { it as? ScriptDependencyExtractor }.map { it.extractor.constructors.first().call() }
|
||||
template.annotations.mapNotNull { it as? ScriptDependencyResolver }.map { it.extractor.constructors.first().call() }
|
||||
}
|
||||
|
||||
private val dependencies by lazy {
|
||||
private val dependencies: List<KotlinScriptExternalDependencies> by lazy {
|
||||
dependenciesExtractors.mapNotNull { it(context) }
|
||||
}
|
||||
|
||||
override fun getScriptDependenciesClasspath(): List<String> = dependencies.flatMap { it.classpath }
|
||||
override fun <TF> getDependenciesFor(file: TF, project: Project): KotlinScriptExternalDependencies? {
|
||||
val fileAnnotations = getAnnotationEntries(file, project)
|
||||
val fileDeps = dependenciesExtractors.mapNotNull { it(fileAnnotations, context) }
|
||||
return KotlinScriptExternalDependenciesUnion(dependencies + fileDeps)
|
||||
}
|
||||
|
||||
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.absolutePath)
|
||||
?: throw java.lang.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 java.lang.IllegalArgumentException("Unable to load PSI from ${file.canonicalPath}")
|
||||
return getAnnotationEntriesFromPsiFile(psifile)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user