[kotlin] K2: add info about compilation errors for main.kts

^KTIJ-31585 Fixed

GitOrigin-RevId: fc38549039df3a8ade960da003804a52be5d54a3
This commit is contained in:
Vlad Koshkin
2024-10-08 22:07:53 +02:00
committed by intellij-monorepo-bot
parent aaab9175dd
commit d68eb44858
15 changed files with 212 additions and 56 deletions

View File

@@ -18,11 +18,7 @@ import org.jetbrains.kotlin.idea.base.plugin.KotlinPluginModeProvider
import org.jetbrains.kotlin.idea.base.projectStructure.KotlinBaseProjectStructureBundle
import org.jetbrains.kotlin.idea.base.projectStructure.KotlinResolveScopeEnlarger
import org.jetbrains.kotlin.idea.base.projectStructure.LibraryInfoCache
import org.jetbrains.kotlin.idea.base.projectStructure.moduleInfo.IdeaModuleInfo
import org.jetbrains.kotlin.idea.base.projectStructure.moduleInfo.LanguageSettingsOwner
import org.jetbrains.kotlin.idea.base.projectStructure.moduleInfo.LibraryInfo
import org.jetbrains.kotlin.idea.base.projectStructure.moduleInfo.ModuleOrigin
import org.jetbrains.kotlin.idea.base.projectStructure.moduleInfo.SdkInfo
import org.jetbrains.kotlin.idea.base.projectStructure.moduleInfo.*
import org.jetbrains.kotlin.idea.base.projectStructure.sourceModuleInfos
import org.jetbrains.kotlin.idea.base.scripting.getLanguageVersionSettings
import org.jetbrains.kotlin.idea.base.scripting.getPlatform
@@ -87,20 +83,15 @@ data class ScriptModuleInfo(
addAll(libraryInfoCache[it])
}
if (KotlinPluginModeProvider.isK1Mode()) {
val scriptDependentModules = ScriptAdditionalIdeaDependenciesProvider.getRelatedModules(scriptFile, project)
scriptDependentModules.forEach {
addAll(it.sourceModuleInfos)
}
val scriptDependentModules = ScriptAdditionalIdeaDependenciesProvider.getRelatedModules(scriptFile, project)
scriptDependentModules.forEach {
addAll(it.sourceModuleInfos)
}
if (KotlinPluginModeProvider.isK1Mode()) {
val dependenciesInfo = ScriptDependenciesInfo.ForFile(project, scriptFile, scriptDefinition)
add(dependenciesInfo)
} else {
val scriptDependentModules = ScriptAdditionalIdeaDependenciesProvider.getRelatedModules(scriptFile, project)
scriptDependentModules.forEach {
addAll(it.sourceModuleInfos)
}
scriptFile.scriptLibraryDependencies(project).forEach(::add)
}

View File

@@ -11,7 +11,6 @@ import com.intellij.psi.PsiManager
import com.intellij.ui.EditorNotifications
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.jetbrains.kotlin.idea.base.plugin.KotlinPluginModeProvider
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.UserDataProperty
import org.jetbrains.kotlin.scripting.resolve.ScriptReportSink
@@ -22,33 +21,47 @@ class IdeScriptReportSink(
private val coroutineScope: CoroutineScope
) : ScriptReportSink {
override fun attachReports(scriptFile: VirtualFile, reports: List<ScriptDiagnostic>) {
if (getReports(scriptFile) == reports) return
if (getScriptReports(scriptFile) == reports) return
// TODO: persist errors between launches?
scriptFile.scriptDiagnostics = reports
if (KotlinPluginModeProvider.isK1Mode()) {
coroutineScope.launch {
readAction {
PsiManager.getInstance(project).findFile(scriptFile)?.let {
DaemonCodeAnalyzer.getInstance(project).restart(it)
}
EditorNotifications.getInstance(project).updateAllNotifications()
coroutineScope.launch {
readAction {
PsiManager.getInstance(project).findFile(scriptFile)?.let {
DaemonCodeAnalyzer.getInstance(project).restart(it)
}
EditorNotifications.getInstance(project).updateAllNotifications()
}
}
}
}
companion object {
fun getReports(file: VirtualFile): List<ScriptDiagnostic> {
return file.scriptDiagnostics ?: emptyList()
}
class KotlinScriptingReportsSink(
val project: Project,
) : ScriptReportSink {
override fun attachReports(scriptFile: VirtualFile, reports: List<ScriptDiagnostic>) {
if (getScriptReports(scriptFile) == reports) return
fun getReports(file: KtFile): List<ScriptDiagnostic> {
return file.originalFile.virtualFile?.scriptDiagnostics ?: emptyList()
}
private var VirtualFile.scriptDiagnostics: List<ScriptDiagnostic>? by UserDataProperty(Key.create("KOTLIN_SCRIPT_DIAGNOSTICS"))
scriptFile.scriptDiagnostics = reports
}
}
}
fun getScriptReports(file: VirtualFile): List<ScriptDiagnostic> {
return file.scriptDiagnostics ?: emptyList()
}
fun getScriptReports(file: KtFile): List<ScriptDiagnostic> {
return file.originalFile.virtualFile?.scriptDiagnostics ?: emptyList()
}
fun drainScriptReports(file: KtFile): List<ScriptDiagnostic> {
val virtualFile = file.originalFile.virtualFile
val diagnostics = virtualFile?.scriptDiagnostics ?: emptyList()
virtualFile.scriptDiagnostics = emptyList()
return diagnostics
}
private var VirtualFile.scriptDiagnostics: List<ScriptDiagnostic>? by UserDataProperty(Key.create("KOTLIN_SCRIPT_DIAGNOSTICS"))

View File

@@ -40,7 +40,7 @@ import kotlin.script.experimental.api.makeFailureResult
internal class IdeScriptDependenciesProvider(project: Project) : ScriptConfigurationsProvider(project) {
override fun getScriptConfigurationResult(file: KtFile): ScriptCompilationConfigurationResult? {
val configuration = getScriptConfiguration(file)
val reports = IdeScriptReportSink.getReports(file)
val reports = getScriptReports(file)
if (configuration == null && reports.isNotEmpty()) {
return makeFailureResult(reports)
}

View File

@@ -29,7 +29,7 @@ internal class ScriptTrafficLightRendererContributor : TrafficLightRendererContr
val status = super.getDaemonCodeAnalyzerStatus(severityRegistrar)
if (KotlinPluginModeProvider.isK2Mode()) {
if (ScriptConfigurationsProviderImpl.getInstanceIfCreated(project)?.getScriptConfiguration(file) == null) {
if (ScriptConfigurationsProviderImpl.getInstanceIfCreated(project)?.getScriptConfigurationResult(file) == null) {
status.reasonWhySuspended = KotlinBaseScriptingBundle.message("text.loading.kotlin.script.configuration")
status.errorAnalyzingFinished = false
}

View File

@@ -315,7 +315,7 @@ class DefaultScriptingSupport(manager: CompositeScriptConfigurationManager) : De
file: VirtualFile,
newReports: List<ScriptDiagnostic>
) {
val oldReports = IdeScriptReportSink.getReports(file)
val oldReports = getScriptReports(file)
if (oldReports != newReports) {
scriptingDebugLog(file) { "new script reports = $newReports" }

View File

@@ -17,6 +17,7 @@ import org.jetbrains.kotlin.idea.core.script.ScriptDependenciesModificationTrack
import org.jetbrains.kotlin.idea.core.util.toPsiFile
import org.jetbrains.kotlin.psi.KtFile
import java.util.concurrent.atomic.AtomicReference
import kotlin.script.experimental.api.ResultWithDiagnostics
open class BaseScriptModel(
open val virtualFile: VirtualFile
@@ -26,14 +27,18 @@ abstract class ScriptDependenciesSource<T : BaseScriptModel>(open val project: P
val currentConfigurationsData = AtomicReference(ScriptDependenciesData())
protected abstract fun resolveDependencies(scripts: Iterable<T>): ScriptDependenciesData
protected abstract suspend fun updateModules(dependencies: ScriptDependenciesData, storage: MutableEntityStorage? = null)
suspend fun updateDependenciesAndCreateModules(scripts: Iterable<T>, storage: MutableEntityStorage? = null) {
project.waitForSmartMode()
val dependencies = resolveDependencies(scripts)
val configurationData = resolveDependencies(scripts)
updateModules(dependencies, storage)
currentConfigurationsData.set(dependencies)
if (configurationData.configurations.all { it.value is ResultWithDiagnostics.Success }) {
updateModules(configurationData, storage)
}
currentConfigurationsData.set(configurationData)
ScriptConfigurationsProviderImpl.getInstance(project).notifySourceUpdated()

View File

@@ -13,7 +13,7 @@ import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.idea.core.script.IdeScriptReportSink
import org.jetbrains.kotlin.idea.core.script.getScriptReports
import org.jetbrains.kotlin.idea.script.ScriptDiagnosticFixProvider
import org.jetbrains.kotlin.idea.util.application.isApplicationInternalMode
import org.jetbrains.kotlin.psi.KtFile
@@ -28,7 +28,7 @@ class ScriptExternalHighlightingPass(
val document = document
if (!file.isScript()) return
val reports = IdeScriptReportSink.getReports(file)
val reports = getScriptReports(file)
val infos = reports.mapNotNull { scriptDiagnostic ->
val (startOffset, endOffset) = scriptDiagnostic.location?.let { computeOffsets(document, it) } ?: (0 to 0)
@@ -37,7 +37,7 @@ class ScriptExternalHighlightingPass(
@Suppress("HardCodedStringLiteral")
val message = scriptDiagnostic.message + exceptionMessage
val severity = scriptDiagnostic.severity.convertSeverity() ?: return@mapNotNull null
val annotation = HighlightInfo.newHighlightInfo(com.intellij.codeInsight.daemon.impl.HighlightInfo.convertSeverity(severity))
val annotation = HighlightInfo.newHighlightInfo(HighlightInfo.convertSeverity(severity))
.range(startOffset,endOffset)
.descriptionAndTooltip(message)
if (startOffset == endOffset) {

View File

@@ -13,6 +13,7 @@
<extensions defaultExtensionNs="com.intellij">
<highlightVisitor implementation="org.jetbrains.kotlin.idea.highlighting.KotlinDiagnosticHighlightVisitor"/>
<highlightVisitor implementation="org.jetbrains.kotlin.idea.highlighting.KotlinSemanticHighlightingVisitor"/>
<highlightVisitor implementation="org.jetbrains.kotlin.idea.highlighting.KotlinScriptHighlightingVisitor"/>
<highlightUsagesHandlerFactory implementation="org.jetbrains.kotlin.idea.highlighting.KotlinHighlightExitPointsHandlerFactory"/>

View File

@@ -0,0 +1,134 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.highlighting
import com.intellij.codeInsight.daemon.impl.HighlightInfo
import com.intellij.codeInsight.daemon.impl.HighlightVisitor
import com.intellij.codeInsight.daemon.impl.analysis.HighlightInfoHolder
import com.intellij.lang.annotation.HighlightSeverity
import com.intellij.lang.annotation.HighlightSeverity.*
import com.intellij.openapi.diagnostic.ControlFlowException
import com.intellij.openapi.editor.Document
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.idea.core.script.drainScriptReports
import org.jetbrains.kotlin.idea.script.ScriptDiagnosticFixProvider
import org.jetbrains.kotlin.idea.util.application.isApplicationInternalMode
import org.jetbrains.kotlin.psi.KtFile
import kotlin.script.experimental.api.ScriptDiagnostic
import kotlin.script.experimental.api.SourceCode
class KotlinScriptHighlightingVisitor : HighlightVisitor {
private lateinit var diagnosticRanges: MutableMap<TextRange, MutableList<HighlightInfo.Builder>>
private var holder: HighlightInfoHolder? = null
override fun suitableForFile(file: PsiFile): Boolean {
return file is KtFile && file.isScript()
}
override fun visit(element: PsiElement) {
val elementRange = element.textRange
// show diagnostics with textRanges under this element range
// assumption: highlight visitors call visit() method in the post-order (children first)
// note that after this visitor finished, `diagnosticRanges` will be empty, because all diagnostics are inside the file range, by definition
val iterator = diagnosticRanges.iterator()
for (entry in iterator) {
if (entry.key in elementRange) {
val diagnostics = entry.value
for (builder in diagnostics) {
holder!!.add(builder.create())
}
iterator.remove()
}
}
}
override fun analyze(
file: PsiFile,
updateWholeFile: Boolean,
holder: HighlightInfoHolder,
action: Runnable
): Boolean {
this.holder = holder
val ktFile = file as KtFile
val reports = drainScriptReports(file)
diagnosticRanges = reports.mapNotNull { scriptDiagnostic ->
val (startOffset, endOffset) = scriptDiagnostic.location?.let<SourceCode.Location, Pair<Int, Int>> {
computeOffsets(
ktFile.fileDocument,
it
)
} ?: (0 to 0)
val exception = scriptDiagnostic.exception
val exceptionMessage = if (exception != null) " ($exception)" else ""
@Suppress("HardCodedStringLiteral")
val message = scriptDiagnostic.message + exceptionMessage
val severity = scriptDiagnostic.severity.convertSeverity() ?: return@mapNotNull null
val annotation = HighlightInfo.newHighlightInfo(HighlightInfo.convertSeverity(severity))
.range(startOffset, endOffset)
.descriptionAndTooltip(message)
if (startOffset == endOffset) {
// if range is empty, show notification panel in editor
annotation.fileLevelAnnotation()
}
for (provider in ScriptDiagnosticFixProvider.EP_NAME.extensionList) {
provider.provideFixes(scriptDiagnostic).forEach {
annotation.registerFix(it, null, null, null, null)
}
}
TextRange(startOffset, endOffset) to mutableListOf(annotation)
}.toMap().toMutableMap()
try {
action.run()
} catch (e: Throwable) {
if (e is ControlFlowException) throw e
// TODO: Port KotlinHighlightingSuspender to K2 to avoid the issue with infinite highlighting loop restart
throw e
} finally {
// do not leak Editor, since KotlinDiagnosticHighlightVisitor is an app-level extension
this.holder = null
diagnosticRanges.clear()
}
return true
}
override fun clone(): HighlightVisitor = KotlinScriptHighlightingVisitor()
private fun computeOffsets(document: Document, position: SourceCode.Location): Pair<Int, Int> {
val startOffset = position.start.absolutePos
?: run {
val startLine = position.start.line.coerceLineIn(document)
document.offsetBy(startLine, position.start.col)
}
val endOffset = position.end?.absolutePos
?: run {
val startLine = position.start.line.coerceLineIn(document)
val endLine = position.end?.line?.coerceAtLeast(startLine)?.coerceLineIn(document) ?: startLine
document.offsetBy(
endLine,
position.end?.col ?: document.getLineEndOffset(endLine)
).coerceAtLeast(startOffset)
}
return startOffset to endOffset
}
private fun Int.coerceLineIn(document: Document) = coerceIn(0, document.lineCount - 1)
private fun Document.offsetBy(line: Int, col: Int): Int =
(getLineStartOffset(line) + col).coerceIn(getLineStartOffset(line), getLineEndOffset(line))
private fun ScriptDiagnostic.Severity.convertSeverity(): HighlightSeverity? = when (this) {
ScriptDiagnostic.Severity.FATAL -> ERROR
ScriptDiagnostic.Severity.ERROR -> ERROR
ScriptDiagnostic.Severity.WARNING -> WARNING
ScriptDiagnostic.Severity.INFO -> INFORMATION
ScriptDiagnostic.Severity.DEBUG -> if (isApplicationInternalMode()) INFORMATION else null
}
}

View File

@@ -18,8 +18,8 @@ import org.jetbrains.kotlin.idea.base.projectStructure.matches
import org.jetbrains.kotlin.idea.base.projectStructure.moduleInfo
import org.jetbrains.kotlin.idea.base.projectStructure.moduleInfo.NotUnderContentRootModuleInfo
import org.jetbrains.kotlin.idea.base.util.KotlinPlatformUtils
import org.jetbrains.kotlin.idea.core.script.IdeScriptReportSink
import org.jetbrains.kotlin.idea.core.script.ScriptDependenciesModificationTracker
import org.jetbrains.kotlin.idea.core.script.getScriptReports
import org.jetbrains.kotlin.psi.KtCodeFragment
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.definitions.ScriptConfigurationsProvider
@@ -80,10 +80,10 @@ private fun KtFile.calculateShouldHighlightScript(): Boolean {
if (shouldDefinitelyHighlight()) return true
return (!KotlinPlatformUtils.isCidr // There is no Java support in CIDR. So do not highlight errors in KTS if running in CIDR.
&& !IdeScriptReportSink.getReports(this).any { it.severity == ScriptDiagnostic.Severity.FATAL }
&& !getScriptReports(this).any { it.severity == ScriptDiagnostic.Severity.FATAL }
&& isConfigurationLoaded()
&& RootKindFilter.projectSources.copy(includeScriptsOutsideSourceRoots = true).matches(this))
}
private fun KtFile.isConfigurationLoaded(): Boolean =
ScriptConfigurationsProvider.getInstance(project)?.getScriptConfiguration(this) != null
ScriptConfigurationsProvider.getInstance(project)?.getScriptConfigurationResult(this) != null

View File

@@ -28,7 +28,7 @@ class KotlinDefaultHighlightingSettingsProvider : DefaultHighlightingSettingProv
return when {
psiFile is KtFile ->
when {
psiFile.isScript() && ScriptConfigurationsProvider.getInstance(project)?.getScriptConfiguration(psiFile) == null ->
psiFile.isScript() && ScriptConfigurationsProvider.getInstance(project)?.getScriptConfigurationResult(psiFile) == null ->
FileHighlightingSetting.SKIP_HIGHLIGHTING
psiFile.isCompiled -> FileHighlightingSetting.SKIP_INSPECTION

View File

@@ -1,6 +1,7 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.script.k2
import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.roots.ProjectRootManager
@@ -17,6 +18,7 @@ import org.jetbrains.kotlin.idea.core.script.k2.BaseScriptModel
import org.jetbrains.kotlin.idea.core.script.k2.ScriptDependenciesData
import org.jetbrains.kotlin.idea.core.script.k2.ScriptDependenciesSource
import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition
import org.jetbrains.kotlin.scripting.resolve.ScriptReportSink
import org.jetbrains.kotlin.scripting.resolve.VirtualFileScriptSource
import org.jetbrains.kotlin.scripting.resolve.refineScriptCompilationConfiguration
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
@@ -46,10 +48,15 @@ class CustomScriptDependenciesSource(override val project: Project) : ScriptDepe
}
}
return ScriptDependenciesData(
configurations,
sdks = sdk?.homePath?.let<@NonNls String, Map<Path, Sdk>> { mapOf(Path.of(it) to sdk) } ?: emptyMap()
)
configurations.forEach { script, result ->
project.service<ScriptReportSink>().attachReports(script, result.reports)
}
return currentConfigurationsData.get().compose(
ScriptDependenciesData(
configurations,
sdks = sdk?.homePath?.let<@NonNls String, Map<Path, Sdk>> { mapOf(Path.of(it) to sdk) } ?: emptyMap()
))
}
override suspend fun updateModules(dependencies: ScriptDependenciesData, storage: MutableEntityStorage?) {

View File

@@ -120,7 +120,7 @@ abstract class AbstractScriptConfigurationLoadingTest : AbstractScriptConfigurat
}
protected fun assertReports(expected: String, file: KtFile = myFile as KtFile) {
val actual = IdeScriptReportSink.getReports(file.virtualFile).single().message
val actual = getScriptReports(file.virtualFile).single().message
assertEquals("reports", expected, actual)
}

View File

@@ -22,10 +22,10 @@ import org.jdom.Element
import org.jetbrains.kotlin.idea.base.highlighting.shouldHighlightFile
import org.jetbrains.kotlin.idea.base.plugin.artifacts.TestKotlinArtifacts
import org.jetbrains.kotlin.idea.completion.test.KotlinCompletionTestCase
import org.jetbrains.kotlin.idea.core.script.IdeScriptReportSink
import org.jetbrains.kotlin.idea.core.script.SCRIPT_DEFINITIONS_SOURCES
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager.Companion.updateScriptDependenciesSynchronously
import org.jetbrains.kotlin.idea.core.script.ScriptDefinitionsManager
import org.jetbrains.kotlin.idea.core.script.getScriptReports
import org.jetbrains.kotlin.idea.core.script.settings.KotlinScriptingSettings
import org.jetbrains.kotlin.idea.script.AbstractScriptConfigurationTest.Companion.useDefaultTemplate
import org.jetbrains.kotlin.idea.test.*
@@ -325,7 +325,7 @@ abstract class AbstractScriptConfigurationTest : KotlinCompletionTestCase() {
}
protected fun checkHighlighting(file: KtFile = myFile as KtFile) {
val reports = IdeScriptReportSink.getReports(file)
val reports = getScriptReports(file)
val isFatalErrorPresent = reports.any { it.severity == ScriptDiagnostic.Severity.FATAL }
assert(isFatalErrorPresent || file.shouldHighlightFile()) {
"Highlighting is switched off for ${file.virtualFile.path}\n" +

View File

@@ -18,6 +18,11 @@
area="IDEA_PROJECT"
dynamic="true"/>
<extensionPoint
qualifiedName="org.jetbrains.kotlin.scriptDiagnosticFixProvider"
interface="org.jetbrains.kotlin.idea.script.ScriptDiagnosticFixProvider"
dynamic="true"/>
<extensionPoint
qualifiedName="org.jetbrains.kotlin.scripting.definitions.scriptDefinitionProvider"
interface="org.jetbrains.kotlin.scripting.definitions.ScriptDefinitionProvider"
@@ -87,7 +92,7 @@
serviceImplementation="org.jetbrains.kotlin.idea.core.script.k2.ScriptConfigurationsProviderImpl"/>
<projectService serviceInterface="org.jetbrains.kotlin.scripting.resolve.ScriptReportSink"
serviceImplementation="org.jetbrains.kotlin.idea.core.script.IdeScriptReportSink"/>
serviceImplementation="org.jetbrains.kotlin.idea.core.script.KotlinScriptingReportsSink"/>
<cachesInvalidator implementation="org.jetbrains.kotlin.idea.core.script.ucache.ScriptCacheDependenciesFileInvalidator"/>