Kapt: Keep KDoc comments in Java stubs (KT-21205)
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
+11
-3
@@ -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) {
|
||||
|
||||
+97
@@ -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)
|
||||
}
|
||||
}
|
||||
+6
@@ -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");
|
||||
|
||||
+9
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user