Support line location information in KAPT for top level declarations

This CL fixes a bug in KAPT's MAP_DIAGNOSTIC_LOCATIONS flag where the
location mapping for top level elements didn't work.

I also needed to changes the Kapt3IntegrationTest to copy input files,
otherwise, it cannot map file paths since the mock project has no root
location.

Cleanup tmp folders in KAPT tests, update Kapt3IT test for new lines.

^KT-47934 Fixed
Test: KotlinKapt3IntegrationTests#testErrorLocationMapping
This commit is contained in:
Yigit Boyar
2021-07-30 17:05:28 -07:00
committed by Yahor Berdnikau
parent ec3a6e7f02
commit 95f990adab
11 changed files with 209 additions and 6 deletions
@@ -479,9 +479,9 @@ open class Kapt3IT : Kapt3BaseIT() {
val actual = getErrorMessages() val actual = getErrorMessages()
// try as 0 starting lines first, then as 1 starting line // try as 0 starting lines first, then as 1 starting line
try { try {
Assert.assertEquals(genKotlinErrorString(2, 6), actual) Assert.assertEquals(genKotlinErrorString(3, 6), actual)
} catch (e: AssertionError) { } catch (e: AssertionError) {
Assert.assertEquals(genKotlinErrorString(3, 7), actual) Assert.assertEquals(genKotlinErrorString(4, 7), actual)
} }
} }
} }
@@ -168,6 +168,9 @@ class KaptStubLineInformation {
} }
private fun JCTree.isLocatedInside(declaration: JCTree): Boolean { private fun JCTree.isLocatedInside(declaration: JCTree): Boolean {
if (this === declaration) {
return true
}
var found = false var found = false
declaration.accept(object : TreeScanner() { declaration.accept(object : TreeScanner() {
@@ -17,10 +17,15 @@
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 org.jetbrains.kotlin.base.kapt3.DetectMemoryLeaksMode import org.jetbrains.kotlin.base.kapt3.DetectMemoryLeaksMode
import org.jetbrains.kotlin.base.kapt3.KaptOptions import org.jetbrains.kotlin.base.kapt3.KaptOptions
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSourceLocation
import org.jetbrains.kotlin.cli.common.messages.MessageCollector import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.codegen.ClassBuilderMode import org.jetbrains.kotlin.codegen.ClassBuilderMode
import org.jetbrains.kotlin.codegen.CodegenTestFiles
import org.jetbrains.kotlin.codegen.GenerationUtils import org.jetbrains.kotlin.codegen.GenerationUtils
import org.jetbrains.kotlin.codegen.OriginCollectingClassBuilderFactory import org.jetbrains.kotlin.codegen.OriginCollectingClassBuilderFactory
import org.jetbrains.kotlin.kapt3.AbstractKapt3Extension import org.jetbrains.kotlin.kapt3.AbstractKapt3Extension
@@ -34,13 +39,16 @@ 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.stubs.ClassFileToSourceStubConverter.KaptStub import org.jetbrains.kotlin.kapt3.stubs.ClassFileToSourceStubConverter.KaptStub
import org.jetbrains.kotlin.kapt3.util.MessageCollectorBackedKaptLogger import org.jetbrains.kotlin.kapt3.util.MessageCollectorBackedKaptLogger
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.test.ConfigurationKind import org.jetbrains.kotlin.test.ConfigurationKind
import org.jetbrains.kotlin.test.KotlinTestUtils import org.jetbrains.kotlin.test.KotlinTestUtils
import org.jetbrains.kotlin.test.util.KtTestUtil
import org.jetbrains.kotlin.test.util.trimTrailingWhitespacesAndAddNewlineAtEOF import org.jetbrains.kotlin.test.util.trimTrailingWhitespacesAndAddNewlineAtEOF
import org.jetbrains.kotlin.utils.PathUtil import org.jetbrains.kotlin.utils.PathUtil
import java.io.File import java.io.File
import java.nio.file.Files import java.nio.file.Files
import java.util.ArrayList
import javax.annotation.processing.Completion import javax.annotation.processing.Completion
import javax.annotation.processing.ProcessingEnvironment import javax.annotation.processing.ProcessingEnvironment
import javax.annotation.processing.Processor import javax.annotation.processing.Processor
@@ -123,6 +131,24 @@ abstract class AbstractKotlinKapt3IntegrationTest : KotlinKapt3TestBase() {
} }
} }
override fun loadMultiFiles(files: List<TestFile>) {
val project = myEnvironment.project
val psiManager = PsiManager.getInstance(project)
val tmpDir = tmpDir("kaptTest")
val ktFiles = ArrayList<KtFile>(files.size)
for (file in files.sorted()) {
if (file.name.endsWith(".kt")) {
val tmpKtFile = File(tmpDir, file.name).apply { writeText(file.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>) { override fun doMultiFileTest(wholeFile: File, files: List<TestFile>) {
val txtFile = File(wholeFile.parentFile, wholeFile.nameWithoutExtension + ".it.txt") val txtFile = File(wholeFile.parentFile, wholeFile.nameWithoutExtension + ".it.txt")
@@ -167,8 +193,15 @@ abstract class AbstractKotlinKapt3IntegrationTest : KotlinKapt3TestBase() {
} }
} }
protected inner class Kapt3ExtensionForTests(options: KaptOptions, private val processors: List<Processor>) : AbstractKapt3Extension( protected inner class Kapt3ExtensionForTests(
options, MessageCollectorBackedKaptLogger(options), compilerConfiguration = myEnvironment.configuration options: KaptOptions,
private val processors: List<Processor>,
val messageCollector: LoggingMessageCollector = LoggingMessageCollector()
) : AbstractKapt3Extension(
options, MessageCollectorBackedKaptLogger(
flags = options,
messageCollector = messageCollector
), compilerConfiguration = myEnvironment.configuration
) { ) {
internal var savedStubs: String? = null internal var savedStubs: String? = null
internal var savedBindings: Map<String, KaptJavaFileObject>? = null internal var savedBindings: Map<String, KaptJavaFileObject>? = null
@@ -204,4 +237,28 @@ abstract class AbstractKotlinKapt3IntegrationTest : KotlinKapt3TestBase() {
super.saveIncrementalData(kaptContext, messageCollector, converter) super.saveIncrementalData(kaptContext, messageCollector, converter)
} }
} }
class LoggingMessageCollector : MessageCollector {
private val _messages = mutableListOf<Message>()
val messages: List<Message>
get() = _messages
override fun clear() {
_messages.clear()
}
override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageSourceLocation?) {
_messages.add(Message(severity, message, location))
}
override fun hasErrors() = _messages.any {
it.severity.isError
}
data class Message(
val severity: CompilerMessageSeverity,
val message: String,
val location: CompilerMessageSourceLocation?
)
}
} }
@@ -89,7 +89,7 @@ abstract class AbstractKotlinKapt3Test : KotlinKapt3TestBase() {
val project = myEnvironment.project val project = myEnvironment.project
val psiManager = PsiManager.getInstance(project) val psiManager = PsiManager.getInstance(project)
val tmpDir = KtTestUtil.tmpDir("kaptTest") val tmpDir = tmpDir("kaptTest")
val ktFiles = ArrayList<KtFile>(files.size) val ktFiles = ArrayList<KtFile>(files.size)
for (file in files.sorted()) { for (file in files.sorted()) {
@@ -123,7 +123,7 @@ abstract class AbstractKotlinKapt3Test : KotlinKapt3TestBase() {
projectBaseDir = project.basePath?.let { File(it) } projectBaseDir = project.basePath?.let { File(it) }
compileClasspath.addAll(PathUtil.getJdkClassesRootsFromCurrentJre() + PathUtil.kotlinPathsForIdeaPlugin.stdlibPath) compileClasspath.addAll(PathUtil.getJdkClassesRootsFromCurrentJre() + PathUtil.kotlinPathsForIdeaPlugin.stdlibPath)
sourcesOutputDir = KtTestUtil.tmpDir("kaptRunner") sourcesOutputDir = tmpDir("kaptRunner")
classesOutputDir = sourcesOutputDir classesOutputDir = sourcesOutputDir
stubsOutputDir = sourcesOutputDir stubsOutputDir = sourcesOutputDir
incrementalDataOutputDir = sourcesOutputDir incrementalDataOutputDir = sourcesOutputDir
@@ -35,6 +35,11 @@ public class IrKotlinKaptContextTestGenerated extends AbstractIrKotlinKaptContex
runTest("plugins/kapt3/kapt3-compiler/testData/kotlinRunner/DefaultParameterValues.kt"); runTest("plugins/kapt3/kapt3-compiler/testData/kotlinRunner/DefaultParameterValues.kt");
} }
@TestMetadata("ErrorLocationMapping.kt")
public void testErrorLocationMapping() throws Exception {
runTest("plugins/kapt3/kapt3-compiler/testData/kotlinRunner/ErrorLocationMapping.kt");
}
@TestMetadata("NestedClasses.kt") @TestMetadata("NestedClasses.kt")
public void testNestedClasses() throws Exception { public void testNestedClasses() throws Exception {
runTest("plugins/kapt3/kapt3-compiler/testData/kotlinRunner/NestedClasses.kt"); runTest("plugins/kapt3/kapt3-compiler/testData/kotlinRunner/NestedClasses.kt");
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.kapt3.test package org.jetbrains.kotlin.kapt3.test
import junit.framework.TestCase
import org.jetbrains.kotlin.kapt3.javac.KaptJavaFileObject import org.jetbrains.kotlin.kapt3.javac.KaptJavaFileObject
import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
@@ -29,6 +30,8 @@ import javax.lang.model.element.ElementKind
import javax.lang.model.element.ExecutableElement import javax.lang.model.element.ExecutableElement
import javax.lang.model.element.TypeElement import javax.lang.model.element.TypeElement
import javax.lang.model.element.VariableElement import javax.lang.model.element.VariableElement
import javax.lang.model.util.ElementFilter
import javax.tools.Diagnostic
import kotlin.system.exitProcess import kotlin.system.exitProcess
class KotlinKapt3IntegrationTests : AbstractKotlinKapt3IntegrationTest(), CustomJdkTestLauncher { class KotlinKapt3IntegrationTests : AbstractKotlinKapt3IntegrationTest(), CustomJdkTestLauncher {
@@ -103,6 +106,68 @@ class KotlinKapt3IntegrationTests : AbstractKotlinKapt3IntegrationTest(), Custom
} }
} }
@Test
fun testErrorLocationMapping() {
val diagnostics = diagnosticsTest("ErrorLocationMapping", "MyAnnotation") { _, _, processingEnv ->
val subject = processingEnv.elementUtils.getTypeElement("Subject")
assertNotNull(subject)
processingEnv.messager.printMessage(
Diagnostic.Kind.NOTE,
"note on class",
subject
)
// report error on the field as well
val field = ElementFilter.fieldsIn(
subject.enclosedElements
).firstOrNull {
it.simpleName.toString() == "field"
}
processingEnv.messager.printMessage(
Diagnostic.Kind.NOTE,
"note on field",
field
)
}
diagnostics.assertContainsDiagnostic("ErrorLocationMapping.kt:1: Note: note on class")
diagnostics.assertContainsDiagnostic("ErrorLocationMapping.kt:5: Note: note on field")
}
private fun List<LoggingMessageCollector.Message>.assertContainsDiagnostic(
message: String
) {
assert(
any {
it.message.contains(message)
}
) {
"""
Didn't find expected diagnostic message.
Expected: $message
Diagnostics:
${this.joinToString("\n") { "${it.severity}: ${it.message}" }}
""".trimIndent()
}
}
@Suppress("SameParameterValue")
private fun diagnosticsTest(
name: String,
vararg supportedAnnotations: String,
process: (Set<TypeElement>, RoundEnvironment, ProcessingEnvironment) -> Unit
): List<LoggingMessageCollector.Message> {
lateinit var messageCollector: LoggingMessageCollector
test(
name = name,
supportedAnnotations = supportedAnnotations
) { typeElements, roundEnv, processingEnv ->
val kaptExtension = AnalysisHandlerExtension.getInstances(myEnvironment.project).firstIsInstance<Kapt3ExtensionForTests>()
messageCollector = kaptExtension.messageCollector
process(typeElements, roundEnv, processingEnv)
}
return messageCollector.messages
}
private fun bindingsTest(name: String, test: (File, File, Map<String, KaptJavaFileObject>) -> Unit) { private fun bindingsTest(name: String, test: (File, File, Map<String, KaptJavaFileObject>) -> Unit) {
test(name, "test.MyAnnotation") { _, _, _ -> test(name, "test.MyAnnotation") { _, _, _ ->
val kaptExtension = AnalysisHandlerExtension.getInstances(myEnvironment.project).firstIsInstance<Kapt3ExtensionForTests>() val kaptExtension = AnalysisHandlerExtension.getInstances(myEnvironment.project).firstIsInstance<Kapt3ExtensionForTests>()
@@ -7,11 +7,13 @@ package org.jetbrains.kotlin.kapt3.test
import org.jetbrains.kotlin.base.kapt3.KaptFlag import org.jetbrains.kotlin.base.kapt3.KaptFlag
import org.jetbrains.kotlin.codegen.CodegenTestCase import org.jetbrains.kotlin.codegen.CodegenTestCase
import org.jetbrains.kotlin.test.util.KtTestUtil
import java.io.File import java.io.File
abstract class KotlinKapt3TestBase : CodegenTestCase() { abstract class KotlinKapt3TestBase : CodegenTestCase() {
val kaptFlagsToAdd = mutableListOf<KaptFlag>() val kaptFlagsToAdd = mutableListOf<KaptFlag>()
val kaptFlagsToRemove = mutableListOf<KaptFlag>() val kaptFlagsToRemove = mutableListOf<KaptFlag>()
private val directoriesToCleanup = mutableListOf<File>()
override fun setUp() { override fun setUp() {
super.setUp() super.setUp()
@@ -19,6 +21,11 @@ abstract class KotlinKapt3TestBase : CodegenTestCase() {
kaptFlagsToRemove.clear() kaptFlagsToRemove.clear()
} }
override fun tearDown() {
directoriesToCleanup.forEach(File::deleteRecursively)
super.tearDown()
}
protected fun isFlagEnabled(flagName: String, testFile: File): Boolean { protected fun isFlagEnabled(flagName: String, testFile: File): Boolean {
val stringToCheck = "// $flagName" val stringToCheck = "// $flagName"
return testFile.useLines { lines -> lines.any { it.trim() == stringToCheck } } return testFile.useLines { lines -> lines.any { it.trim() == stringToCheck } }
@@ -37,6 +44,10 @@ abstract class KotlinKapt3TestBase : CodegenTestCase() {
} }
} }
protected fun tmpDir(name: String): File {
return KtTestUtil.tmpDir(name).also(directoriesToCleanup::add)
}
override fun doTest(filePath: String) { override fun doTest(filePath: String) {
val testFile = File(filePath) val testFile = File(filePath)
@@ -34,6 +34,11 @@ public class KotlinKaptContextTestGenerated extends AbstractKotlinKaptContextTes
runTest("plugins/kapt3/kapt3-compiler/testData/kotlinRunner/DefaultParameterValues.kt"); runTest("plugins/kapt3/kapt3-compiler/testData/kotlinRunner/DefaultParameterValues.kt");
} }
@TestMetadata("ErrorLocationMapping.kt")
public void testErrorLocationMapping() throws Exception {
runTest("plugins/kapt3/kapt3-compiler/testData/kotlinRunner/ErrorLocationMapping.kt");
}
@TestMetadata("NestedClasses.kt") @TestMetadata("NestedClasses.kt")
public void testNestedClasses() throws Exception { public void testNestedClasses() throws Exception {
runTest("plugins/kapt3/kapt3-compiler/testData/kotlinRunner/NestedClasses.kt"); runTest("plugins/kapt3/kapt3-compiler/testData/kotlinRunner/NestedClasses.kt");
@@ -0,0 +1,45 @@
import java.lang.System;
@kotlin.Metadata()
@java.lang.annotation.Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
public abstract @interface MyAnnotation {
}
////////////////////
import java.lang.System;
@kotlin.Metadata()
public final class Subject {
@org.jetbrains.annotations.NotNull()
private final java.lang.String field = "";
@org.jetbrains.annotations.NotNull()
private final java.lang.String annotationTrigger = "";
public Subject() {
super();
}
@org.jetbrains.annotations.NotNull()
public final java.lang.String getField() {
return null;
}
@org.jetbrains.annotations.NotNull()
public final java.lang.String getAnnotationTrigger() {
return null;
}
@MyAnnotation()
@java.lang.Deprecated()
public static void getAnnotationTrigger$annotations() {
}
}
////////////////////
package error;
public final class NonExistentClass {
}
@@ -0,0 +1,11 @@
class Subject {
val field:String = ""
@MyAnnotation
val annotationTrigger: String = ""
}
internal annotation class MyAnnotation