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:
Mathias Quintero
2020-05-20 13:42:08 +02:00
committed by Ilya Chernikov
parent 1539128c3f
commit 83087291df
19 changed files with 550 additions and 85 deletions
@@ -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)
}