Add API to get locations of collected script annotations
#KT-38404 fixed also: - Add wrapper class for the location combined with the location id - Add source code location parameters to external dependency resolvers - Add tests for locations in annotations - Add tests for order of annotation resolution for dependencies resolvers
This commit is contained in:
committed by
Ilya Chernikov
parent
1539128c3f
commit
83087291df
+47
-9
@@ -6,11 +6,13 @@
|
||||
package org.jetbrains.kotlin.scripting.resolve
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.editor.Document
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.openapi.vfs.CharsetToolkit
|
||||
import com.intellij.openapi.vfs.LocalFileSystem
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.PsiManager
|
||||
import com.intellij.testFramework.LightVirtualFile
|
||||
@@ -18,6 +20,8 @@ import kotlinx.coroutines.runBlocking
|
||||
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||
import org.jetbrains.kotlin.psi.KtAnnotationEntry
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.psiUtil.endOffset
|
||||
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
||||
import org.jetbrains.kotlin.scripting.definitions.KotlinScriptDefinition
|
||||
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
|
||||
import org.jetbrains.kotlin.scripting.withCorrectExtension
|
||||
@@ -41,7 +45,9 @@ internal fun VirtualFile.loadAnnotations(
|
||||
): List<Annotation> =
|
||||
// TODO_R: report error on failure to load annotation class
|
||||
ApplicationManager.getApplication().runReadAction<List<Annotation>> {
|
||||
this.getAnnotationEntries(project).construct(classLoader, acceptedAnnotations, project)
|
||||
this.getAnnotationEntries(project)
|
||||
.construct(classLoader, acceptedAnnotations, project)
|
||||
.map { it.first }
|
||||
}
|
||||
|
||||
internal fun VirtualFile.getAnnotationEntries(project: Project): Iterable<KtAnnotationEntry> {
|
||||
@@ -55,8 +61,7 @@ internal fun VirtualFile.getAnnotationEntries(project: Project): Iterable<KtAnno
|
||||
* The implementation of the SourceCode for a script located in a virtual file
|
||||
*/
|
||||
open class VirtualFileScriptSource(val virtualFile: VirtualFile, private val preloadedText: String? = null) :
|
||||
FileBasedScriptSource()
|
||||
{
|
||||
FileBasedScriptSource() {
|
||||
override val file: File get() = File(virtualFile.path)
|
||||
override val externalLocation: URL get() = URL(virtualFile.url)
|
||||
override val text: String by lazy { preloadedText ?: virtualFile.inputStream.bufferedReader().readText() }
|
||||
@@ -354,27 +359,60 @@ fun getScriptCollectedData(
|
||||
jvmGetScriptingClass(ann, contextClassLoader, hostConfiguration) as? KClass<Annotation> // TODO errors
|
||||
}
|
||||
}.orEmpty()
|
||||
val annotations = scriptFile.annotationEntries.construct(contextClassLoader, acceptedAnnotations, project)
|
||||
val annotations = scriptFile.annotationEntries.construct(
|
||||
contextClassLoader,
|
||||
acceptedAnnotations,
|
||||
project,
|
||||
scriptFile.viewProvider.document,
|
||||
scriptFile.virtualFilePath
|
||||
)
|
||||
return ScriptCollectedData(
|
||||
mapOf(
|
||||
ScriptCollectedData.foundAnnotations to annotations
|
||||
ScriptCollectedData.collectedAnnotations to annotations,
|
||||
ScriptCollectedData.foundAnnotations to annotations.map { it.annotation }
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun Iterable<KtAnnotationEntry>.construct(
|
||||
classLoader: ClassLoader?, acceptedAnnotations: List<KClass<out Annotation>>, project: Project, document: Document?, filePath: String
|
||||
): List<ScriptSourceAnnotation<*>> = construct(classLoader, acceptedAnnotations, project).map { (annotation, psiAnn) ->
|
||||
ScriptSourceAnnotation(
|
||||
annotation = annotation,
|
||||
location = document?.let { document ->
|
||||
SourceCode.LocationWithId(
|
||||
codeLocationId = filePath,
|
||||
locationInText = psiAnn.location(document)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun Iterable<KtAnnotationEntry>.construct(
|
||||
classLoader: ClassLoader?, acceptedAnnotations: List<KClass<out Annotation>>, project: Project
|
||||
): List<Annotation> =
|
||||
): List<Pair<Annotation, KtAnnotationEntry>> =
|
||||
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 {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
(constructAnnotation(
|
||||
constructAnnotation(
|
||||
psiAnn,
|
||||
(classLoader ?: ClassLoader.getSystemClassLoader()).loadClass(it.qualifiedName).kotlin as KClass<out Annotation>,
|
||||
project
|
||||
))
|
||||
) to psiAnn
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun PsiElement.location(document: Document): SourceCode.Location {
|
||||
val start = document.offsetToPosition(startOffset)
|
||||
val end = if (endOffset > startOffset) document.offsetToPosition(endOffset) else null
|
||||
return SourceCode.Location(start, end)
|
||||
}
|
||||
|
||||
private fun Document.offsetToPosition(offset: Int): SourceCode.Position {
|
||||
val line = getLineNumber(offset)
|
||||
val column = offset - getLineStartOffset(line)
|
||||
return SourceCode.Position(line + 1, column + 1, offset)
|
||||
}
|
||||
Reference in New Issue
Block a user