Kapt: Replace original Javac diagnostic messages with those with Kotlin location mapped

There is no Messages dialog in newer versions of IDEA/Android Studio in which the error messages were mapped before. The new Build window shows only the original locations, so now we need to replace Java file diagnostics with ones mapped to Kotlin source files.

The side effect is that diagnostics on the same locations are automatically merged.
This commit is contained in:
Yan Zhulanow
2018-01-15 22:49:58 +09:00
parent b0e97de8a8
commit 5668a7af92
8 changed files with 95 additions and 69 deletions
@@ -57,9 +57,8 @@ fun parse(lineText: String, reader: OutputLineReader, messages: MutableList<Mess
val column = if (matcher.groupCount() >= 2) matcher.group(2)?.toInt() ?: 1 else 1
if (line != null) {
val mainPosition = SourceFilePosition(file, SourcePosition(line, column, column))
val kotlinKaptPosition = findAdditionalKotlinLocationFromKapt(message)
return addMessage(Message(getMessageKind(severity), message.trim(), kotlinKaptPosition ?: mainPosition), messages)
val position = SourceFilePosition(file, SourcePosition(line, column, column))
return addMessage(Message(getMessageKind(severity), message.trim(), position), messages)
}
}
@@ -73,26 +72,9 @@ fun parse(lineText: String, reader: OutputLineReader, messages: MutableList<Mess
return false
}
private fun findAdditionalKotlinLocationFromKapt(message: String): SourceFilePosition? {
for (lineText in message.lines()) {
val matcher = ADDITIONAL_KOTLIN_POSITION_FROM_KAPT_PATTERN.matcher(lineText.trim()).takeIf { it.matches() } ?: continue
val filePath = matcher.group(1) ?: break
// Check both Unix (/...) and Windows (C:\...) patterns
if (!filePath.startsWith("/") && !filePath.drop(1).startsWith(":\\")) break
val line = matcher.group(2).toIntOrNull() ?: break
val column = matcher.group(3).toIntOrNull() ?: break
return SourceFilePosition(File(filePath), SourcePosition(line, column, column))
}
return null
}
private val COLON = ":"
private val KOTLIN_POSITION_PATTERN = Pattern.compile("\\(([0-9]*), ([0-9]*)\\)")
private val JAVAC_POSITION_PATTERN = Pattern.compile("([0-9]+)")
private val ADDITIONAL_KOTLIN_POSITION_FROM_KAPT_PATTERN = Pattern.compile("^Kotlin location: (.+): \\((\\d+), (\\d+)\\)$")
private fun String.amendNextLinesIfNeeded(reader: OutputLineReader): String {
var nextLine = reader.readLine()
@@ -17,7 +17,6 @@
package org.jetbrains.kotlin.kapt3.javac
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.sun.tools.javac.tree.JCTree
import com.sun.tools.javac.util.*
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticType
@@ -29,10 +28,11 @@ import org.jetbrains.kotlin.kapt3.stubs.KaptStubLineInformation
import org.jetbrains.kotlin.kapt3.stubs.KotlinPosition
import org.jetbrains.kotlin.kapt3.util.MessageCollectorBackedWriter
import org.jetbrains.kotlin.kapt3.util.isJava9OrLater
import java.io.File
import java.io.PrintWriter
import java.io.*
import javax.tools.Diagnostic
import javax.tools.JavaFileObject
import javax.tools.JavaFileObject.Kind
import javax.tools.SimpleJavaFileObject
import com.sun.tools.javac.util.List as JavacList
class KaptJavaLog(
@@ -101,18 +101,39 @@ class KaptJavaLog(
val kotlinPosition = stubLineInfo.getPositionInKotlinFile(sourceFile, targetElement.tree)
val kotlinFile = kotlinPosition?.let { getKotlinSourceFile(it) }
if (kotlinPosition != null && kotlinFile != null) {
val locationMessage = "$KOTLIN_LOCATION_PREFIX${kotlinFile.absolutePath}: (${kotlinPosition.line}, ${kotlinPosition.column})"
val locationDiagnostic = diags.note(null as DiagnosticSource?, null, "proc.messager", locationMessage)
val wrappedDiagnostic = JCDiagnostic.MultilineDiagnostic(diagnostic, JavacList.of(locationDiagnostic))
val flags = JCDiagnostic.DiagnosticFlag.values().filterTo(mutableSetOf(), diagnostic::isFlagSet)
reportDiagnostic(wrappedDiagnostic)
return // Avoid reporting the diagnostic twice
val kotlinDiagnostic = diags.create(
diagnostic.type,
diagnostic.lintCategory,
flags,
DiagnosticSource(KotlinFileObject(kotlinFile), this),
JCDiagnostic.SimpleDiagnosticPosition(kotlinPosition.pos),
diagnostic.code.stripCompilerKeyPrefix(),
*diagnostic.args
)
reportDiagnostic(kotlinDiagnostic)
// Avoid reporting the diagnostic twice
return
}
}
reportDiagnostic(diagnostic)
}
private fun String.stripCompilerKeyPrefix(): String {
for (kind in listOf("err", "warn", "misc", "note")) {
val prefix = "compiler.$kind."
if (startsWith(prefix)) {
return drop(prefix.length)
}
}
return this
}
private fun reportDiagnostic(diagnostic: JCDiagnostic) {
if (diagnostic.kind == Diagnostic.Kind.ERROR) {
val oldErrors = nerrors
@@ -249,4 +270,14 @@ private fun JCDiagnostic.Factory.errorJava9Aware(
else {
this.error(source, pos, key, *args)
}
}
private data class KotlinFileObject(val file: File) : SimpleJavaFileObject(file.toURI(), Kind.SOURCE) {
override fun openOutputStream() = file.outputStream()
override fun openWriter() = file.writer()
override fun openInputStream() = file.inputStream()
override fun getCharContent(ignoreEncodingErrors: Boolean) = file.readText()
override fun getLastModified() = file.lastModified()
override fun openReader(ignoreEncodingErrors: Boolean) = file.reader()
override fun delete() = file.delete()
}
@@ -19,15 +19,13 @@ package org.jetbrains.kotlin.kapt3.stubs
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.sun.tools.javac.tree.JCTree
import org.jetbrains.kotlin.diagnostics.DiagnosticUtils
import org.jetbrains.kotlin.kapt3.KaptContext
import org.jetbrains.org.objectweb.asm.tree.ClassNode
import org.jetbrains.org.objectweb.asm.tree.FieldNode
import org.jetbrains.org.objectweb.asm.tree.MethodNode
import java.io.*
import java.util.*
data class KotlinPosition(val path: String, val isRelativePath: Boolean, val line: Int, val column: Int)
data class KotlinPosition(val path: String, val isRelativePath: Boolean, val pos: Int)
private typealias LineInfoMap = MutableMap<String, KotlinPosition>
@@ -67,10 +65,9 @@ class KaptLineMappingCollector(private val kaptContext: KaptContext<*>) {
val fqName = ois.readUTF()
val path = ois.readUTF()
val isRelative = ois.readBoolean()
val line = ois.readInt()
val column = ois.readInt()
val pos = ois.readInt()
lineInfo[fqName] = KotlinPosition(path, isRelative, line, column)
lineInfo[fqName] = KotlinPosition(path, isRelative, pos)
}
val signatureCount = ois.readInt()
@@ -119,13 +116,9 @@ class KaptLineMappingCollector(private val kaptContext: KaptContext<*>) {
private fun register(fqName: String, psiElement: PsiElement) {
val textRange = psiElement.textRange ?: return
val psiFile = psiElement.containingFile
val lineAndColumn = DiagnosticUtils.getLineAndColumnInPsiFile(psiFile, textRange)
if (lineAndColumn.line >= 0 && lineAndColumn.column >= 0) {
val (path, isRelative) = getFilePathRelativePreferred(psiFile)
lineInfo[fqName] = KotlinPosition(path, isRelative, lineAndColumn.line, lineAndColumn.column)
}
val (path, isRelative) = getFilePathRelativePreferred(psiElement.containingFile)
lineInfo[fqName] = KotlinPosition(path, isRelative, textRange.startOffset)
}
private fun getFilePathRelativePreferred(file: PsiFile): Pair<String, Boolean> {
@@ -156,8 +149,7 @@ class KaptLineMappingCollector(private val kaptContext: KaptContext<*>) {
oos.writeUTF(fqName)
oos.writeUTF(kotlinPosition.path)
oos.writeBoolean(kotlinPosition.isRelativePath)
oos.writeInt(kotlinPosition.line)
oos.writeInt(kotlinPosition.column)
oos.writeInt(kotlinPosition.pos)
}
oos.writeInt(signatureInfo.size)
@@ -17,16 +17,20 @@
package org.jetbrains.kotlin.kapt3.test
import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.psi.PsiManager
import com.sun.tools.javac.comp.CompileStates
import com.sun.tools.javac.tree.JCTree.JCCompilationUnit
import com.sun.tools.javac.util.JCDiagnostic
import com.sun.tools.javac.util.Log
import junit.framework.TestCase
import org.jetbrains.kotlin.checkers.CheckerTestUtil
import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot
import org.jetbrains.kotlin.codegen.CodegenTestCase
import org.jetbrains.kotlin.codegen.CodegenTestFiles
import org.jetbrains.kotlin.codegen.GenerationUtils
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.kapt3.Kapt3BuilderFactory
@@ -38,6 +42,7 @@ import org.jetbrains.kotlin.kapt3.parseJavaFiles
import org.jetbrains.kotlin.kapt3.prettyPrint
import org.jetbrains.kotlin.kapt3.stubs.ClassFileToSourceStubConverter
import org.jetbrains.kotlin.kapt3.util.KaptLogger
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension
import org.jetbrains.kotlin.resolve.jvm.extensions.PartialAnalysisHandlerExtension
import org.jetbrains.kotlin.test.ConfigurationKind
@@ -65,6 +70,26 @@ abstract class AbstractKotlinKapt3Test : CodegenTestCase() {
}
}
override fun loadMultiFiles(files: List<TestFile>) {
val project = myEnvironment.project
val psiManager = PsiManager.getInstance(project)
val tmpDir = Files.createTempDirectory("kaptTest").toFile()
tempFiles += tmpDir
val ktFiles = ArrayList<KtFile>(files.size)
for (file in files.sorted()) {
if (file.name.endsWith(".kt")) {
val content = CheckerTestUtil.parseDiagnosedRanges(file.content, ArrayList<CheckerTestUtil.DiagnosedRange>(0))
val tmpKtFile = File(tmpDir, file.name).apply { writeText(content) }
val virtualFile = StandardFileSystems.local().findFileByPath(tmpKtFile.path) ?: error("Can't find ${file.name}")
ktFiles.add(psiManager.findFile(virtualFile) as? KtFile ?: error("Can't load ${file.name}"))
}
}
myFiles = CodegenTestFiles.create(ktFiles)
}
override fun doMultiFileTest(wholeFile: File, files: List<TestFile>, javaFilesDir: File?) {
val javaSources = javaFilesDir?.let { arrayOf(it) } ?: emptyArray()
@@ -102,7 +127,7 @@ abstract class AbstractKotlinKapt3Test : CodegenTestCase() {
throw RuntimeException(e)
} finally {
javaFiles.forEach { it.delete() }
tempFiles.forEach { it.delete() }
tempFiles.forEach { if (it.isFile) it.delete() else it.deleteRecursively() }
tempFiles.clear()
kaptContext.close()
}
@@ -213,13 +238,16 @@ open class AbstractClassFileToSourceStubConverterTest : AbstractKotlinKapt3Test(
val actualErrors = log.reportedDiagnostics
.filter { it.type == JCDiagnostic.DiagnosticType.ERROR }
.map {
val location = it.subdiagnostics
.firstOrNull { it.getMessage(Locale.US).startsWith("Kotlin location:") }
?.getMessage(Locale.US)
// Unfortunately, we can't use the file name as it can contain temporary prefix
val name = it.source?.name?.substringAfterLast("/") ?: ""
val kind = when (name.substringAfterLast(".").toLowerCase()) {
"kt" -> "kotlin"
"java" -> "java"
else -> "other"
}
val javaLocation = "(${it.lineNumber};${it.columnNumber}) "
val message = javaLocation + it.getMessage(Locale.US).lines().first()
if (location != null) "$message ($location)" else message
val javaLocation = "($kind:${it.lineNumber}:${it.columnNumber}) "
javaLocation + it.getMessage(Locale.US).lines().first()
}
.map { "// " + EXPECTED_ERROR + it }
.sorted()
@@ -1,21 +1,14 @@
// CORRECT_ERROR_TYPES
// EXPECTED_ERROR(11;12) cannot find symbol (Kotlin location: /errorLocationMapping.kt: (34, 5))
// EXPECTED_ERROR(11;34) cannot find symbol (Kotlin location: /errorLocationMapping.kt: (26, 62))
// EXPECTED_ERROR(19;18) cannot find symbol (Kotlin location: /errorLocationMapping.kt: (26, 50))
// EXPECTED_ERROR(23;18) cannot find symbol (Kotlin location: /errorLocationMapping.kt: (33, 5))
// EXPECTED_ERROR(24;33) cannot find symbol (Kotlin location: /errorLocationMapping.kt: (26, 62))
// EXPECTED_ERROR(28;5) cannot find symbol (Kotlin location: /errorLocationMapping.kt: (33, 5))
// EXPECTED_ERROR(30;5) cannot find symbol (Kotlin location: /errorLocationMapping.kt: (26, 34))
// EXPECTED_ERROR(31;30) cannot find symbol (Kotlin location: /errorLocationMapping.kt: (26, 34))
// EXPECTED_ERROR(32;18) cannot find symbol (Kotlin location: /errorLocationMapping.kt: (34, 5))
// EXPECTED_ERROR(37;5) cannot find symbol (Kotlin location: /errorLocationMapping.kt: (34, 5))
// EXPECTED_ERROR(45;5) cannot find symbol (Kotlin location: /errorLocationMapping.kt: (37, 5))
// EXPECTED_ERROR(50;5) cannot find symbol (Kotlin location: /errorLocationMapping.kt: (39, 5))
// EXPECTED_ERROR(5;11) cannot find symbol (Kotlin location: /errorLocationMapping.kt: (23, 1))
// EXPECTED_ERROR(60;18) cannot find symbol (Kotlin location: /errorLocationMapping.kt: (42, 5))
// EXPECTED_ERROR(9;12) cannot find symbol (Kotlin location: /errorLocationMapping.kt: (33, 5))
// EXPECTED_ERROR(9;19) cannot find symbol (Kotlin location: /errorLocationMapping.kt: (26, 50))
// EXPECTED_ERROR(kotlin:16:1) cannot find symbol
// EXPECTED_ERROR(kotlin:19:34) cannot find symbol
// EXPECTED_ERROR(kotlin:19:50) cannot find symbol
// EXPECTED_ERROR(kotlin:19:62) cannot find symbol
// EXPECTED_ERROR(kotlin:26:5) cannot find symbol
// EXPECTED_ERROR(kotlin:27:5) cannot find symbol
// EXPECTED_ERROR(kotlin:30:5) cannot find symbol
// EXPECTED_ERROR(kotlin:32:5) cannot find symbol
// EXPECTED_ERROR(kotlin:35:5) cannot find symbol
@file:Suppress("UNRESOLVED_REFERENCE", "ANNOTATION_PARAMETER_MUST_BE_CONST", "NON_CONST_VAL_USED_IN_CONSTANT_EXPRESSION")
import kotlin.reflect.KClass
@@ -1,5 +1,5 @@
// EXPECTED_ERROR(-1;-1) 'WHI-TE' is an invalid Java enum value name
// EXPECTED_ERROR(6;20) cannot find symbol (Kotlin location: /invalidFieldName.kt: (8, 1))
// EXPECTED_ERROR(kotlin:8:1) cannot find symbol
// EXPECTED_ERROR(other:-1:-1) 'WHI-TE' is an invalid Java enum value name
enum class Color {
BLACK, `WHI-TE`
@@ -1,5 +1,5 @@
// CORRECT_ERROR_TYPES
// EXPECTED_ERROR(8;5) cannot find symbol (Kotlin location: /maxErrorCount.kt: (9, 5))
// EXPECTED_ERROR(kotlin:9:5) cannot find symbol
// JAVAC_OPTION -Xmaxerrs=1
@file:Suppress("UNRESOLVED_REFERENCE")
@@ -1,4 +1,4 @@
// EXPECTED_ERROR(-1;-1) '60x60' is an invalid Java enum value name
// EXPECTED_ERROR(other:-1:-1) '60x60' is an invalid Java enum value name
class `:)` {
lateinit val f: String