Kapt: Keep KDoc comments in Java stubs (KT-21205)

This commit is contained in:
Yan Zhulanow
2017-11-14 20:03:12 +09:00
parent af57ab8119
commit 37543deefa
10 changed files with 404 additions and 28 deletions
@@ -57,6 +57,7 @@ class KaptContext<out GState : GenerationState?>(
fileManager = context.get(JavaFileManager::class.java) as JavacFileManager
compiler = JavaCompiler.instance(context) as KaptJavaCompiler
compiler.keepComments = true
ClassReader.instance(context).saveParameterNames = true
@@ -98,6 +98,8 @@ class ClassFileToSourceStubConverter(
private val anonymousTypeHandler = AnonymousTypeHandler(this)
private val kdocCommentKeeper = KDocCommentKeeper(kaptContext)
private var done = false
fun convert(): JavacList<JCCompilationUnit> {
@@ -180,6 +182,7 @@ class ClassFileToSourceStubConverter(
val classes = JavacList.of<JCTree>(classDeclaration)
val topLevel = treeMaker.TopLevelJava9Aware(packageClause, imports + classes)
topLevel.docComments = kdocCommentKeeper.docCommentTable
KaptJavaFileObject(topLevel, classDeclaration).apply {
topLevel.sourcefile = this
@@ -347,7 +350,7 @@ class ClassFileToSourceStubConverter(
genericType.typeParameters,
if (hasSuperClass) genericType.superClass else null,
genericType.interfaces,
enumValues + fields + methods + nestedClasses)
enumValues + fields + methods + nestedClasses).keepComments(clazz)
}
private tailrec fun checkIfShouldBeIgnored(type: Type): Boolean {
@@ -453,7 +456,7 @@ class ClassFileToSourceStubConverter(
lineMappings.registerField(containingClass, field)
return treeMaker.VarDef(modifiers, treeMaker.name(name), typeExpression, initializer)
return treeMaker.VarDef(modifiers, treeMaker.name(name), typeExpression, initializer).keepComments(field)
}
private fun convertMethod(
@@ -563,7 +566,7 @@ class ClassFileToSourceStubConverter(
return treeMaker.MethodDef(
modifiers, treeMaker.name(name), returnType, genericSignature.typeParameters,
genericSignature.parameterTypes, genericSignature.exceptionTypes,
body, defaultValue)
body, defaultValue).keepComments(method)
}
private fun isIgnored(annotations: List<AnnotationNode>?): Boolean {
@@ -883,6 +886,11 @@ class ClassFileToSourceStubConverter(
Type.DOUBLE_TYPE -> 0.0
else -> null
}
private fun <T : JCTree> T.keepComments(node: Any): T {
kdocCommentKeeper.saveKDocComment(this, node)
return this
}
}
private fun Any?.isOfPrimiviteType(): Boolean = when(this) {
@@ -0,0 +1,97 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.kapt3.stubs
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiWhiteSpace
import com.intellij.psi.impl.source.tree.LeafPsiElement
import com.sun.tools.javac.parser.Tokens
import com.sun.tools.javac.tree.DCTree
import com.sun.tools.javac.tree.DocCommentTable
import com.sun.tools.javac.tree.JCTree
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
import org.jetbrains.kotlin.descriptors.PropertyAccessorDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.kapt3.KaptContext
import org.jetbrains.kotlin.kdoc.lexer.KDocTokens
import org.jetbrains.kotlin.kdoc.psi.api.KDoc
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.org.objectweb.asm.tree.FieldNode
import org.jetbrains.org.objectweb.asm.tree.MethodNode
class KDocCommentKeeper(private val kaptContext: KaptContext<*>) {
val docCommentTable: DocCommentTable = KaptDocCommentTable()
fun saveKDocComment(tree: JCTree, node: Any) {
val origin = kaptContext.origins[node] ?: return
val psiElement = origin.element as? KtDeclaration ?: return
val descriptor = origin.descriptor
val docComment = psiElement.docComment ?: return
if (descriptor is ConstructorDescriptor && psiElement is KtClassOrObject) {
// We don't want the class comment to be duplicated on <init>()
return
}
if (node is MethodNode
&& psiElement is KtProperty
&& descriptor is PropertyAccessorDescriptor
&& kaptContext.bindingContext[BindingContext.BACKING_FIELD_REQUIRED, descriptor.correspondingProperty] == true
) {
// Do not place the smae documentation on backing field and property accessors
return
}
if (node is FieldNode && psiElement is KtObjectDeclaration && descriptor == null) {
// Do not write KDoc on object instance field
return
}
docCommentTable.putComment(tree, KDocComment(extractCommentText(docComment)))
}
private fun extractCommentText(docComment: KDoc): String {
return docComment.children.dropWhile { it is PsiWhiteSpace || it.isKDocStart() }
.dropLastWhile { it is PsiWhiteSpace || it.isKDocEnd() }
.joinToString("") { it.text }
}
private fun PsiElement.isKDocStart() = this is LeafPsiElement && elementType == KDocTokens.START
private fun PsiElement.isKDocEnd() = this is LeafPsiElement && elementType == KDocTokens.END
}
private class KDocComment(val body: String) : Tokens.Comment {
override fun getSourcePos(index: Int) = -1
override fun getStyle() = Tokens.Comment.CommentStyle.JAVADOC
override fun getText() = body
override fun isDeprecated() = false
}
private class KaptDocCommentTable : DocCommentTable {
private val table = mutableMapOf<JCTree, Tokens.Comment>()
override fun hasComment(tree: JCTree) = tree in table
override fun getComment(tree: JCTree) = table[tree]
override fun getCommentText(tree: JCTree) = getComment(tree)?.text
override fun getCommentTree(tree: JCTree): DCTree.DCDocComment? = null
override fun putComment(tree: JCTree, c: Tokens.Comment) {
table.put(tree, c)
}
}
@@ -60,6 +60,12 @@ public class ClassFileToSourceStubConverterTestGenerated extends AbstractClassFi
doTest(fileName);
}
@TestMetadata("comments.kt")
public void testComments() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("plugins/kapt3/kapt3-compiler/testData/converter/comments.kt");
doTest(fileName);
}
@TestMetadata("dataClass.kt")
public void testDataClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("plugins/kapt3/kapt3-compiler/testData/converter/dataClass.kt");
@@ -33,6 +33,15 @@ class KotlinKapt3IntegrationTests : AbstractKotlinKapt3IntegrationTest() {
assertEquals("myMethod", annotatedElements.single().simpleName.toString())
}
@Test
fun testComments() = test("Simple", "test.MyAnnotation") { set, roundEnv, env ->
fun commentOf(className: String) = env.elementUtils.getDocComment(env.elementUtils.getTypeElement(className))
assert(commentOf("test.Simple") == " * KDoc comment.\n")
assert(commentOf("test.EnumClass") == null) // simple comment - not saved
assert(commentOf("test.MyAnnotation") == null) // multiline comment - not saved
}
@Test
fun testSimpleStubsAndIncrementalData() = bindingsTest("Simple") { stubsOutputDir, incrementalDataOutputDir, bindings ->
assert(File(stubsOutputDir, "error/NonExistentClass.java").exists())
@@ -0,0 +1,45 @@
/** Test. */
class Test {
/** method(). */
fun method() {}
/** method(int). */
fun method(a: Int) {}
/** method(String). */
fun method(a: String) {}
/** prop. */
val prop: String = ""
/** prop2. */
@Anno
val prop2: String = ""
/** prop3. */
var prop3: String
/** get. */ get() = ""
/** set. */ set(v) {}
}
/**
* Test2
* Multiline
* documentation.
*/
class Test2(val a: String)
class Test3 /** constructor. */ protected constructor(val a: String)
/** Obj. */
object Obj
@Target(AnnotationTarget.PROPERTY)
annotation class Anno
/* simple comment. */
class Test4 {
// method simple comment
fun method() {}
}
@@ -0,0 +1,195 @@
@kotlin.Metadata()
@java.lang.annotation.Target(value = {})
@java.lang.annotation.Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@kotlin.annotation.Target(allowedTargets = {kotlin.annotation.AnnotationTarget.PROPERTY})
@kapt.internal.KaptMetadata()
public abstract @interface Anno {
}
////////////////////
/**
* Obj.
*/
@kotlin.Metadata()
@kapt.internal.KaptMetadata()
public final class Obj {
public static final Obj INSTANCE = null;
@kapt.internal.KaptSignature(value = "<init>()V")
private Obj() {
super();
}
}
////////////////////
/**
* Test.
*/
@kotlin.Metadata()
@kapt.internal.KaptMetadata()
public final class Test {
/**
* prop.
*/
@org.jetbrains.annotations.NotNull()
private final java.lang.String prop = "";
/**
* prop2.
*/
@org.jetbrains.annotations.NotNull()
private final java.lang.String prop2 = "";
/**
* method().
*/
@kapt.internal.KaptSignature(value = "method()V")
public final void method() {
}
/**
* method(int).
*/
@kapt.internal.KaptSignature(value = "method(I)V")
public final void method(int a) {
}
/**
* method(String).
*/
@kapt.internal.KaptSignature(value = "method(Ljava/lang/String;)V")
public final void method(@org.jetbrains.annotations.NotNull()
java.lang.String a) {
}
@org.jetbrains.annotations.NotNull()
@kapt.internal.KaptSignature(value = "getProp()Ljava/lang/String;")
public final java.lang.String getProp() {
return null;
}
@Anno()
@kapt.internal.KaptSignature(value = "prop2$annotations()V")
public static void prop2$annotations() {
}
@org.jetbrains.annotations.NotNull()
@kapt.internal.KaptSignature(value = "getProp2()Ljava/lang/String;")
public final java.lang.String getProp2() {
return null;
}
/**
* get.
*/
@org.jetbrains.annotations.NotNull()
@kapt.internal.KaptSignature(value = "getProp3()Ljava/lang/String;")
public final java.lang.String getProp3() {
return null;
}
/**
* set.
*/
@kapt.internal.KaptSignature(value = "setProp3(Ljava/lang/String;)V")
public final void setProp3(@org.jetbrains.annotations.NotNull()
java.lang.String v) {
}
@kapt.internal.KaptSignature(value = "<init>()V")
public Test() {
super();
}
}
////////////////////
/**
* * Test2
* * Multiline
* * documentation.
*/
@kotlin.Metadata()
@kapt.internal.KaptMetadata()
public final class Test2 {
@org.jetbrains.annotations.NotNull()
private final java.lang.String a = null;
@org.jetbrains.annotations.NotNull()
@kapt.internal.KaptSignature(value = "getA()Ljava/lang/String;")
public final java.lang.String getA() {
return null;
}
@kapt.internal.KaptSignature(value = "<init>(Ljava/lang/String;)V")
public Test2(@org.jetbrains.annotations.NotNull()
java.lang.String a) {
super();
}
}
////////////////////
/**
* constructor.
*/
@kotlin.Metadata()
@kapt.internal.KaptMetadata()
public final class Test3 {
@org.jetbrains.annotations.NotNull()
private final java.lang.String a = null;
@org.jetbrains.annotations.NotNull()
@kapt.internal.KaptSignature(value = "getA()Ljava/lang/String;")
public final java.lang.String getA() {
return null;
}
@kapt.internal.KaptSignature(value = "<init>(Ljava/lang/String;)V")
protected Test3(@org.jetbrains.annotations.NotNull()
java.lang.String a) {
super();
}
}
////////////////////
@kotlin.Metadata()
@kapt.internal.KaptMetadata()
public final class Test4 {
@kapt.internal.KaptSignature(value = "method()V")
public final void method() {
}
@kapt.internal.KaptSignature(value = "<init>()V")
public Test4() {
super();
}
}
////////////////////
package kapt.internal;
public @interface KaptMetadata {
public java.lang.String value();
}
////////////////////
package kapt.internal;
public @interface KaptSignature {
public java.lang.String value();
}
@@ -25,6 +25,34 @@ public @interface KaptSignature {
package test;
/**
* * KDoc comment.
*/
@kotlin.Suppress(names = {"UNRESOLVED_REFERENCE"})
@kotlin.Metadata()
@kapt.internal.KaptMetadata()
public final class Simple {
@MyAnnotation()
@kapt.internal.KaptSignature("myMethod()V")
public final void myMethod() {
}
@kapt.internal.KaptSignature("heavyMethod()I")
public final int heavyMethod() {
return 0;
}
@kapt.internal.KaptSignature("<init>()V")
public Simple() {
super();
}
}
////////////////////
package test;
@kotlin.Metadata()
@kapt.internal.KaptMetadata()
public enum EnumClass {
@@ -62,28 +90,3 @@ public enum EnumClass2 {
java.lang.String blah) {
}
}
////////////////////
package test;
@kotlin.Suppress(names = {"UNRESOLVED_REFERENCE"})
@kotlin.Metadata()
@kapt.internal.KaptMetadata()
public final class Simple {
@MyAnnotation()
@kapt.internal.KaptSignature("myMethod()V")
public final void myMethod() {
}
@kapt.internal.KaptSignature("heavyMethod()I")
public final int heavyMethod() {
return 0;
}
@kapt.internal.KaptSignature("<init>()V")
public Simple() {
super();
}
}
@@ -1,5 +1,8 @@
package test
/**
* KDoc comment.
*/
@Suppress("UNRESOLVED_REFERENCE")
internal class Simple {
@MyAnnotation
@@ -12,8 +15,14 @@ internal class Simple {
}
}
/*
Multi
line
comment
*/
internal annotation class MyAnnotation
// Small comment
internal enum class EnumClass {
BLACK, WHITE
}
@@ -1,5 +1,8 @@
package test;
/**
* KDoc comment.
*/
class Simple {
@MyAnnotation
void myMethod() {