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 val column = if (matcher.groupCount() >= 2) matcher.group(2)?.toInt() ?: 1 else 1
if (line != null) { if (line != null) {
val mainPosition = SourceFilePosition(file, SourcePosition(line, column, column)) val position = SourceFilePosition(file, SourcePosition(line, column, column))
val kotlinKaptPosition = findAdditionalKotlinLocationFromKapt(message) return addMessage(Message(getMessageKind(severity), message.trim(), position), messages)
return addMessage(Message(getMessageKind(severity), message.trim(), kotlinKaptPosition ?: mainPosition), messages)
} }
} }
@@ -73,26 +72,9 @@ fun parse(lineText: String, reader: OutputLineReader, messages: MutableList<Mess
return false 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 COLON = ":"
private val KOTLIN_POSITION_PATTERN = Pattern.compile("\\(([0-9]*), ([0-9]*)\\)") private val KOTLIN_POSITION_PATTERN = Pattern.compile("\\(([0-9]*), ([0-9]*)\\)")
private val JAVAC_POSITION_PATTERN = Pattern.compile("([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 { private fun String.amendNextLinesIfNeeded(reader: OutputLineReader): String {
var nextLine = reader.readLine() var nextLine = reader.readLine()
@@ -17,7 +17,6 @@
package org.jetbrains.kotlin.kapt3.javac package org.jetbrains.kotlin.kapt3.javac
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.sun.tools.javac.tree.JCTree import com.sun.tools.javac.tree.JCTree
import com.sun.tools.javac.util.* import com.sun.tools.javac.util.*
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticType 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.stubs.KotlinPosition
import org.jetbrains.kotlin.kapt3.util.MessageCollectorBackedWriter import org.jetbrains.kotlin.kapt3.util.MessageCollectorBackedWriter
import org.jetbrains.kotlin.kapt3.util.isJava9OrLater import org.jetbrains.kotlin.kapt3.util.isJava9OrLater
import java.io.File import java.io.*
import java.io.PrintWriter
import javax.tools.Diagnostic import javax.tools.Diagnostic
import javax.tools.JavaFileObject import javax.tools.JavaFileObject
import javax.tools.JavaFileObject.Kind
import javax.tools.SimpleJavaFileObject
import com.sun.tools.javac.util.List as JavacList import com.sun.tools.javac.util.List as JavacList
class KaptJavaLog( class KaptJavaLog(
@@ -101,18 +101,39 @@ class KaptJavaLog(
val kotlinPosition = stubLineInfo.getPositionInKotlinFile(sourceFile, targetElement.tree) val kotlinPosition = stubLineInfo.getPositionInKotlinFile(sourceFile, targetElement.tree)
val kotlinFile = kotlinPosition?.let { getKotlinSourceFile(it) } val kotlinFile = kotlinPosition?.let { getKotlinSourceFile(it) }
if (kotlinPosition != null && kotlinFile != null) { if (kotlinPosition != null && kotlinFile != null) {
val locationMessage = "$KOTLIN_LOCATION_PREFIX${kotlinFile.absolutePath}: (${kotlinPosition.line}, ${kotlinPosition.column})" val flags = JCDiagnostic.DiagnosticFlag.values().filterTo(mutableSetOf(), diagnostic::isFlagSet)
val locationDiagnostic = diags.note(null as DiagnosticSource?, null, "proc.messager", locationMessage)
val wrappedDiagnostic = JCDiagnostic.MultilineDiagnostic(diagnostic, JavacList.of(locationDiagnostic))
reportDiagnostic(wrappedDiagnostic) val kotlinDiagnostic = diags.create(
return // Avoid reporting the diagnostic twice 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) 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) { private fun reportDiagnostic(diagnostic: JCDiagnostic) {
if (diagnostic.kind == Diagnostic.Kind.ERROR) { if (diagnostic.kind == Diagnostic.Kind.ERROR) {
val oldErrors = nerrors val oldErrors = nerrors
@@ -250,3 +271,13 @@ private fun JCDiagnostic.Factory.errorJava9Aware(
this.error(source, pos, key, *args) 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.PsiElement
import com.intellij.psi.PsiFile import com.intellij.psi.PsiFile
import com.sun.tools.javac.tree.JCTree import com.sun.tools.javac.tree.JCTree
import org.jetbrains.kotlin.diagnostics.DiagnosticUtils
import org.jetbrains.kotlin.kapt3.KaptContext import org.jetbrains.kotlin.kapt3.KaptContext
import org.jetbrains.org.objectweb.asm.tree.ClassNode import org.jetbrains.org.objectweb.asm.tree.ClassNode
import org.jetbrains.org.objectweb.asm.tree.FieldNode import org.jetbrains.org.objectweb.asm.tree.FieldNode
import org.jetbrains.org.objectweb.asm.tree.MethodNode import org.jetbrains.org.objectweb.asm.tree.MethodNode
import java.io.* 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> private typealias LineInfoMap = MutableMap<String, KotlinPosition>
@@ -67,10 +65,9 @@ class KaptLineMappingCollector(private val kaptContext: KaptContext<*>) {
val fqName = ois.readUTF() val fqName = ois.readUTF()
val path = ois.readUTF() val path = ois.readUTF()
val isRelative = ois.readBoolean() val isRelative = ois.readBoolean()
val line = ois.readInt() val pos = ois.readInt()
val column = ois.readInt()
lineInfo[fqName] = KotlinPosition(path, isRelative, line, column) lineInfo[fqName] = KotlinPosition(path, isRelative, pos)
} }
val signatureCount = ois.readInt() val signatureCount = ois.readInt()
@@ -119,13 +116,9 @@ class KaptLineMappingCollector(private val kaptContext: KaptContext<*>) {
private fun register(fqName: String, psiElement: PsiElement) { private fun register(fqName: String, psiElement: PsiElement) {
val textRange = psiElement.textRange ?: return 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(psiElement.containingFile)
val (path, isRelative) = getFilePathRelativePreferred(psiFile) lineInfo[fqName] = KotlinPosition(path, isRelative, textRange.startOffset)
lineInfo[fqName] = KotlinPosition(path, isRelative, lineAndColumn.line, lineAndColumn.column)
}
} }
private fun getFilePathRelativePreferred(file: PsiFile): Pair<String, Boolean> { private fun getFilePathRelativePreferred(file: PsiFile): Pair<String, Boolean> {
@@ -156,8 +149,7 @@ class KaptLineMappingCollector(private val kaptContext: KaptContext<*>) {
oos.writeUTF(fqName) oos.writeUTF(fqName)
oos.writeUTF(kotlinPosition.path) oos.writeUTF(kotlinPosition.path)
oos.writeBoolean(kotlinPosition.isRelativePath) oos.writeBoolean(kotlinPosition.isRelativePath)
oos.writeInt(kotlinPosition.line) oos.writeInt(kotlinPosition.pos)
oos.writeInt(kotlinPosition.column)
} }
oos.writeInt(signatureInfo.size) oos.writeInt(signatureInfo.size)
@@ -17,16 +17,20 @@
package org.jetbrains.kotlin.kapt3.test package org.jetbrains.kotlin.kapt3.test
import com.intellij.openapi.util.text.StringUtil 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.comp.CompileStates
import com.sun.tools.javac.tree.JCTree.JCCompilationUnit import com.sun.tools.javac.tree.JCTree.JCCompilationUnit
import com.sun.tools.javac.util.JCDiagnostic import com.sun.tools.javac.util.JCDiagnostic
import com.sun.tools.javac.util.Log import com.sun.tools.javac.util.Log
import junit.framework.TestCase 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.MessageRenderer
import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot
import org.jetbrains.kotlin.codegen.CodegenTestCase import org.jetbrains.kotlin.codegen.CodegenTestCase
import org.jetbrains.kotlin.codegen.CodegenTestFiles
import org.jetbrains.kotlin.codegen.GenerationUtils import org.jetbrains.kotlin.codegen.GenerationUtils
import org.jetbrains.kotlin.codegen.state.GenerationState import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.kapt3.Kapt3BuilderFactory 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.prettyPrint
import org.jetbrains.kotlin.kapt3.stubs.ClassFileToSourceStubConverter import org.jetbrains.kotlin.kapt3.stubs.ClassFileToSourceStubConverter
import org.jetbrains.kotlin.kapt3.util.KaptLogger 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.AnalysisHandlerExtension
import org.jetbrains.kotlin.resolve.jvm.extensions.PartialAnalysisHandlerExtension import org.jetbrains.kotlin.resolve.jvm.extensions.PartialAnalysisHandlerExtension
import org.jetbrains.kotlin.test.ConfigurationKind 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?) { override fun doMultiFileTest(wholeFile: File, files: List<TestFile>, javaFilesDir: File?) {
val javaSources = javaFilesDir?.let { arrayOf(it) } ?: emptyArray() val javaSources = javaFilesDir?.let { arrayOf(it) } ?: emptyArray()
@@ -102,7 +127,7 @@ abstract class AbstractKotlinKapt3Test : CodegenTestCase() {
throw RuntimeException(e) throw RuntimeException(e)
} finally { } finally {
javaFiles.forEach { it.delete() } javaFiles.forEach { it.delete() }
tempFiles.forEach { it.delete() } tempFiles.forEach { if (it.isFile) it.delete() else it.deleteRecursively() }
tempFiles.clear() tempFiles.clear()
kaptContext.close() kaptContext.close()
} }
@@ -213,13 +238,16 @@ open class AbstractClassFileToSourceStubConverterTest : AbstractKotlinKapt3Test(
val actualErrors = log.reportedDiagnostics val actualErrors = log.reportedDiagnostics
.filter { it.type == JCDiagnostic.DiagnosticType.ERROR } .filter { it.type == JCDiagnostic.DiagnosticType.ERROR }
.map { .map {
val location = it.subdiagnostics // Unfortunately, we can't use the file name as it can contain temporary prefix
.firstOrNull { it.getMessage(Locale.US).startsWith("Kotlin location:") } val name = it.source?.name?.substringAfterLast("/") ?: ""
?.getMessage(Locale.US) val kind = when (name.substringAfterLast(".").toLowerCase()) {
"kt" -> "kotlin"
"java" -> "java"
else -> "other"
}
val javaLocation = "(${it.lineNumber};${it.columnNumber}) " val javaLocation = "($kind:${it.lineNumber}:${it.columnNumber}) "
val message = javaLocation + it.getMessage(Locale.US).lines().first() javaLocation + it.getMessage(Locale.US).lines().first()
if (location != null) "$message ($location)" else message
} }
.map { "// " + EXPECTED_ERROR + it } .map { "// " + EXPECTED_ERROR + it }
.sorted() .sorted()
@@ -1,21 +1,14 @@
// CORRECT_ERROR_TYPES // CORRECT_ERROR_TYPES
// EXPECTED_ERROR(11;12) cannot find symbol (Kotlin location: /errorLocationMapping.kt: (34, 5)) // EXPECTED_ERROR(kotlin:16:1) cannot find symbol
// EXPECTED_ERROR(11;34) cannot find symbol (Kotlin location: /errorLocationMapping.kt: (26, 62)) // EXPECTED_ERROR(kotlin:19:34) cannot find symbol
// EXPECTED_ERROR(19;18) cannot find symbol (Kotlin location: /errorLocationMapping.kt: (26, 50)) // EXPECTED_ERROR(kotlin:19:50) cannot find symbol
// EXPECTED_ERROR(23;18) cannot find symbol (Kotlin location: /errorLocationMapping.kt: (33, 5)) // EXPECTED_ERROR(kotlin:19:62) cannot find symbol
// EXPECTED_ERROR(24;33) cannot find symbol (Kotlin location: /errorLocationMapping.kt: (26, 62)) // EXPECTED_ERROR(kotlin:26:5) cannot find symbol
// EXPECTED_ERROR(28;5) cannot find symbol (Kotlin location: /errorLocationMapping.kt: (33, 5)) // EXPECTED_ERROR(kotlin:27:5) cannot find symbol
// EXPECTED_ERROR(30;5) cannot find symbol (Kotlin location: /errorLocationMapping.kt: (26, 34)) // EXPECTED_ERROR(kotlin:30:5) cannot find symbol
// EXPECTED_ERROR(31;30) cannot find symbol (Kotlin location: /errorLocationMapping.kt: (26, 34)) // EXPECTED_ERROR(kotlin:32:5) cannot find symbol
// EXPECTED_ERROR(32;18) cannot find symbol (Kotlin location: /errorLocationMapping.kt: (34, 5)) // EXPECTED_ERROR(kotlin:35:5) cannot find symbol
// 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))
@file:Suppress("UNRESOLVED_REFERENCE", "ANNOTATION_PARAMETER_MUST_BE_CONST", "NON_CONST_VAL_USED_IN_CONSTANT_EXPRESSION") @file:Suppress("UNRESOLVED_REFERENCE", "ANNOTATION_PARAMETER_MUST_BE_CONST", "NON_CONST_VAL_USED_IN_CONSTANT_EXPRESSION")
import kotlin.reflect.KClass import kotlin.reflect.KClass
@@ -1,5 +1,5 @@
// EXPECTED_ERROR(-1;-1) 'WHI-TE' is an invalid Java enum value name // EXPECTED_ERROR(kotlin:8:1) cannot find symbol
// EXPECTED_ERROR(6;20) cannot find symbol (Kotlin location: /invalidFieldName.kt: (8, 1)) // EXPECTED_ERROR(other:-1:-1) 'WHI-TE' is an invalid Java enum value name
enum class Color { enum class Color {
BLACK, `WHI-TE` BLACK, `WHI-TE`
@@ -1,5 +1,5 @@
// CORRECT_ERROR_TYPES // 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 // JAVAC_OPTION -Xmaxerrs=1
@file:Suppress("UNRESOLVED_REFERENCE") @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 `:)` { class `:)` {
lateinit val f: String lateinit val f: String