All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jetbrains.kotlin.util.AnalysisExceptions.kt Maven / Gradle / Ivy

There is a newer version: 2.1.0-RC
Show newest version
/*
 * Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
 * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
 */

package org.jetbrains.kotlin.util

import com.intellij.lang.LighterASTNode
import com.intellij.util.diff.FlyweightCapableTreeStructure
import org.jetbrains.kotlin.KtRealSourceElementKind
import org.jetbrains.kotlin.KtSourceElement
import org.jetbrains.kotlin.psi
import org.jetbrains.kotlin.text
import org.jetbrains.kotlin.utils.exceptions.shouldIjPlatformExceptionBeRethrown

val Throwable.classNameAndMessage get() = "${this::class.qualifiedName}: $message"

class SourceCodeAnalysisException(val source: KtSourceElement, override val cause: Throwable) : Exception() {
    override val message get() = cause.classNameAndMessage
}


fun Throwable.wrapIntoSourceCodeAnalysisExceptionIfNeeded(element: KtSourceElement?): Throwable =
    if (this is SourceCodeAnalysisException || shouldIjPlatformExceptionBeRethrown(this) || this is VirtualMachineError) {
        this
    } else {
        when (element?.kind) {
            is KtRealSourceElementKind -> SourceCodeAnalysisException(element, this)
            else -> this
        }
    }

class FileAnalysisException(
    private val path: String,
    override val cause: Throwable,
    private val lineAndOffset: Pair? = null,
) : Exception() {
    override val message
        get(): String {
            val (line, offset) = lineAndOffset ?: return "Somewhere in file $path: ${cause.classNameAndMessage}"
            return "While analysing $path:${line + 1}:${offset + 1}: ${cause.classNameAndMessage}"
        }
}

fun Throwable.wrapIntoFileAnalysisExceptionIfNeeded(
    filePath: String?,
    fileSource: KtSourceElement?,
    linesMapping: (Int) -> Pair?,
) = when {
    filePath == null || fileSource == null -> when (this) {
        is SourceCodeAnalysisException -> error("Sourceless FirFile contains a FirElement with a real source element")
        else -> this
    }
    this is SourceCodeAnalysisException -> when {
        fileSource == source -> FileAnalysisException(filePath, cause)
        source.isDefinitelyNotInsideFile(fileSource) -> reportFileMismatch(source, fileSource, cause)
        else -> FileAnalysisException(filePath, cause, linesMapping(source.startOffset))
    }
    shouldIjPlatformExceptionBeRethrown(this) -> this
    this is FileAnalysisException -> this
    this is VirtualMachineError -> this
    else -> FileAnalysisException(filePath, this)
}

private fun KtSourceElement.isDefinitelyNotInsideFile(fileSource: KtSourceElement): Boolean {
    val thisPsi = psi
    val otherPsi = fileSource.psi

    return when {
        thisPsi != null && otherPsi != null -> thisPsi.containingFile != otherPsi
        else -> !lighterASTNode.isInside(fileSource.lighterASTNode, treeStructure)
    }
}

private fun reportFileMismatch(source: KtSourceElement, fileSource: KtSourceElement, cause: Throwable): Throwable {
    val thisPsi = source.psi
    val otherPsi = fileSource.psi
    val comparison = "This:\n\n${source.text?.asQuote}\n\n...is not present in"

    val expectedFileMessage = if (thisPsi != null && otherPsi != null) {
        val actualPath = thisPsi.containingFile.virtualFile.path
        val expectedPath = otherPsi.containingFile.virtualFile.path
        "$expectedPath, but rather in $actualPath"
    } else {
        "...${fileSource.text?.asQuote}"
    }

    return IllegalStateException(
        "KtSourceElement inside a SourceCodeAnalysisException was matched against the wrong FirFile source. $comparison$expectedFileMessage",
        cause,
    )
}

private val CharSequence.asQuote: String
    get() = split("\n").joinToString("\n") { "> $it" }

private fun LighterASTNode.isInside(other: LighterASTNode, tree: FlyweightCapableTreeStructure): Boolean {
    return generateSequence(tree.getParent(this)) { tree.getParent(it) }.any { it == other }
}

inline fun  withSourceCodeAnalysisExceptionUnwrapping(block: () -> R): R {
    return try {
        block()
    } catch (throwable: Throwable) {
        throw if (throwable is SourceCodeAnalysisException) throwable.cause else throwable
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy