Skip to content

Commit

Permalink
improved error reporting, especially in intellij plugin: now rendered…
Browse files Browse the repository at this point in the history
… inside kdocs
  • Loading branch information
Jolanrensen committed Oct 23, 2023
1 parent 85ba4a0 commit a3d99a6
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,7 @@ open class DocProcessorFailedException(
val processorName: String,
cause: Throwable? = null,
message: String = "Doc processor $processorName failed: ${cause?.message}",
) : RuntimeException(
message,
cause,
)
) : RuntimeException(message, cause)

fun findProcessors(fullyQualifiedNames: List<String>, arguments: Map<String, Any?>): List<DocProcessor> {
val availableProcessors: Set<DocProcessor> = ServiceLoader.load(DocProcessor::class.java).toSet()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -304,34 +304,119 @@ abstract class TagDocProcessor : DocProcessor() {
*/
open class TagDocProcessorFailedException(
processorName: String,
documentable: DocumentableWrapper,
currentDoc: DocContent,
rangeInCurrentDoc: IntRange,
val documentable: DocumentableWrapper,
val currentDoc: DocContent,
val rangeInCurrentDoc: IntRange,
cause: Throwable? = null,
) : DocProcessorFailedException(
processorName = processorName,
cause = cause,
message = buildString {
val rangeInFile = documentable.docFileTextRange
val fileText = documentable.file.readText()
val (line, char) = fileText.getLineAndCharacterOffset(rangeInFile.start)
message = renderMessage(
documentable = documentable,
rangeInCurrentDoc = rangeInCurrentDoc,
processorName = processorName,
currentDoc = currentDoc,
cause = cause,
),
) {
companion object {
private fun renderMessage(
documentable: DocumentableWrapper,
rangeInCurrentDoc: IntRange,
processorName: String,
currentDoc: DocContent,
cause: Throwable?,
): String = buildString {
val docRangeInFile = documentable.docFileTextRange
val fileText = documentable.file.readText()
val (docLine, docChar) = fileText.getLineAndCharacterOffset(docRangeInFile.first)
val (exceptionLine, exceptionChar) = fileText.getLineAndCharacterOffset(rangeInCurrentDoc.first)

fun highlightException(it: String) = "<a href=\"\">$it</a>"

appendLine("Doc processor $processorName failed processing doc:")
appendLine("Doc location: ${documentable.file.absolutePath}:$docLine:$docChar")
appendLine("Exception location: ${documentable.file.absolutePath}:$exceptionLine:$exceptionChar")
appendLine("Tag throwing the exception: ${highlightException(currentDoc.substring(rangeInCurrentDoc))}")
cause?.message?.let {
appendLine("Reason for the exception: $it")
}
appendLine("\u200E")
appendLine("Current state of the doc with the ${highlightException("cause for the exception")}:")
appendLine("--------------------------------------------------")
appendLine(
try {
currentDoc.replaceRange(
range = rangeInCurrentDoc,
replacement = highlightException(currentDoc.substring(rangeInCurrentDoc)),
)
} catch (e: Throwable) {
currentDoc
}.toDoc()
)
appendLine("--------------------------------------------------")
}
}

appendLine("Doc processor $processorName failed processing doc:")
appendLine("(${documentable.file.absolutePath}:$line:$char)")
appendLine("\u200E")
appendLine("Current state of the doc with the <a href=\"\">cause for the exception</a>:")
fun renderMessage(): String =
renderMessage(
documentable = documentable,
rangeInCurrentDoc = rangeInCurrentDoc,
processorName = processorName,
currentDoc = currentDoc,
cause = cause,
)

fun renderDoc(): String = buildString {
val docRangeInFile = documentable.docFileTextRange
val fileText = documentable.file.readText()
val (docLine, docChar) = fileText.getLineAndCharacterOffset(docRangeInFile.first)
val (exceptionLine, exceptionChar) = fileText.getLineAndCharacterOffset(rangeInCurrentDoc.first)

fun highlightException(it: String) = "!!!$it!!!"

val indent = "&nbsp;&nbsp;&nbsp;&nbsp;"
val lineBreak = "\n$indent\n"

appendLine("# Error in DocProcessor")
appendLine("## Doc processor $processorName failed processing doc.")
appendLine()
appendLine("### Doc location:")
appendLine()
appendLine("`${documentable.file.path}:$docLine:$docChar`")
appendLine(lineBreak)
appendLine("### Exception location:")
appendLine()
appendLine("`${documentable.file.absolutePath}:$exceptionLine:$exceptionChar`")
appendLine(lineBreak)
appendLine("### Tag throwing the exception:")
appendLine()
appendLine("**`" + currentDoc.substring(rangeInCurrentDoc) + "`**")
appendLine(lineBreak)
appendLine("### Reason for the exception:")
appendLine()
cause?.message?.let {
for (line in it.lines()) {
appendLine(line)
appendLine()
}
}
appendLine(lineBreak)
appendLine("Current state of the doc with the `${highlightException("cause for the exception")}`:")
appendLine()
appendLine("--------------------------------------------------")
appendLine("```")
appendLine(
try {
currentDoc.replaceRange(
range = rangeInCurrentDoc,
replacement = "<a href=\"\">${currentDoc.substring(rangeInCurrentDoc)}</a>",
replacement = highlightException(currentDoc.substring(rangeInCurrentDoc)),
)
} catch (e: Throwable) {
currentDoc
}.toDoc()
)
appendLine("```")
appendLine("--------------------------------------------------")
cause?.message?.let { appendLine(it) }
},
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,12 @@ import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.LogLevel
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.progress.JobCanceledException
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiDocCommentOwner
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiElementFactory
import com.intellij.psi.PsiManager
import nl.jolanrensen.docProcessor.DocumentableWrapper
import nl.jolanrensen.docProcessor.DocumentablesByPath
import nl.jolanrensen.docProcessor.DocumentablesByPathWithCache
import nl.jolanrensen.docProcessor.MessageBundle
import nl.jolanrensen.docProcessor.ProgrammingLanguage
import nl.jolanrensen.docProcessor.copiedWithFile
import nl.jolanrensen.docProcessor.createFromIntellijOrNull
import nl.jolanrensen.docProcessor.defaultProcessors.ARG_DOC_PROCESSOR
import nl.jolanrensen.docProcessor.defaultProcessors.ARG_DOC_PROCESSOR_LOG_NOT_FOUND
import nl.jolanrensen.docProcessor.defaultProcessors.COMMENT_DOC_PROCESSOR
import nl.jolanrensen.docProcessor.defaultProcessors.INCLUDE_DOC_PROCESSOR
import nl.jolanrensen.docProcessor.defaultProcessors.INCLUDE_FILE_DOC_PROCESSOR
import nl.jolanrensen.docProcessor.defaultProcessors.SAMPLE_DOC_PROCESSOR
import nl.jolanrensen.docProcessor.docComment
import nl.jolanrensen.docProcessor.findProcessors
import nl.jolanrensen.docProcessor.toDoc
import com.intellij.psi.*
import nl.jolanrensen.docProcessor.*
import nl.jolanrensen.docProcessor.defaultProcessors.*
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.caches.resolve.safeAnalyzeNonSourceRootCode
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
Expand All @@ -35,6 +19,7 @@ import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import java.util.*
import java.util.concurrent.CancellationException

@Service(Service.Level.PROJECT)
Expand Down Expand Up @@ -105,11 +90,15 @@ class DocProcessorService(private val project: Project) {
}
}

/**
* Returns a copy of the element with the doc comment modified. If the doc comment is empty, it will be deleted.
* If it didn't exist before, it will be created anew. Return `null` means it could not be modified and the original
* rendering method should be used.
*/
fun getModifiedElement(originalElement: PsiElement): PsiElement? {
try {
// Create a copy of the element, so we can modify it
val psiElement = originalElement.copiedWithFile()

// Create a copy of the element, so we can modify it
val psiElement = originalElement.copiedWithFile()
val docContent = try {
// Create a DocumentableWrapper from the element
val documentableWrapper = DocumentableWrapper.createFromIntellijOrNull(psiElement)
if (documentableWrapper == null) {
Expand All @@ -134,42 +123,47 @@ class DocProcessorService(private val project: Project) {
// Retrieve the original DocumentableWrapper from the results
val doc = results[documentableWrapper.identifier] ?: error("Something went wrong")

// If the new doc is empty, delete the comment
if (doc.docContent.isEmpty()) {
psiElement.docComment?.delete()
return psiElement
}

// If the new doc is not empty, generate a new doc element
val newComment = when (doc.programmingLanguage) {
ProgrammingLanguage.KOTLIN -> KDocElementFactory(project)
.createKDocFromText(
doc.docContent.toDoc()
)

ProgrammingLanguage.JAVA -> PsiElementFactory.getInstance(project)
.createDocCommentFromText(
doc.docContent.toDoc()
)
}

// Replace the old doc element with the new one if it exists, otherwise add a new one
if (psiElement.docComment != null) {
psiElement.docComment?.replace(newComment)
} else {
psiElement.addBefore(newComment, psiElement.firstChild)
}

return psiElement
doc.docContent
} catch (e: ProcessCanceledException) {
return null
} catch (e: CancellationException) {
return null
} catch (e: TagDocProcessorFailedException) {
e.printStackTrace()
// render fancy :)
e.renderDoc()
} catch (e: Throwable) {
e.printStackTrace()
throw e
return null

// instead of throwing the exception, render it inside the kdoc
"```\n$e\n```"
}

// If the new doc is empty, delete the comment
if (docContent.isEmpty()) {
psiElement.docComment?.delete()
return psiElement
}

// If the new doc is not empty, generate a new doc element
val newComment = when (originalElement.programmingLanguage) {
ProgrammingLanguage.KOTLIN ->
KDocElementFactory(project)
.createKDocFromText(docContent.toDoc())

ProgrammingLanguage.JAVA ->
PsiElementFactory.getInstance(project)
.createDocCommentFromText(docContent.toDoc())
}

// Replace the old doc element with the new one if it exists, otherwise add a new one
if (psiElement.docComment != null) {
psiElement.docComment?.replace(newComment)
} else {
psiElement.addBefore(newComment, psiElement.firstChild)
}

return psiElement
}

fun processDocumentablesByPath(sourceDocsByPath: DocumentablesByPath): DocumentablesByPath {
Expand Down

0 comments on commit a3d99a6

Please sign in to comment.