diff --git a/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/KaptContext.kt b/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/KaptContext.kt index 7dd8d8b7991..bf4c945a647 100644 --- a/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/KaptContext.kt +++ b/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/KaptContext.kt @@ -57,6 +57,7 @@ class KaptContext( fileManager = context.get(JavaFileManager::class.java) as JavacFileManager compiler = JavaCompiler.instance(context) as KaptJavaCompiler + compiler.keepComments = true ClassReader.instance(context).saveParameterNames = true diff --git a/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/stubs/ClassFileToSourceStubConverter.kt b/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/stubs/ClassFileToSourceStubConverter.kt index 76b8b9f788b..167a0004a18 100644 --- a/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/stubs/ClassFileToSourceStubConverter.kt +++ b/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/stubs/ClassFileToSourceStubConverter.kt @@ -98,6 +98,8 @@ class ClassFileToSourceStubConverter( private val anonymousTypeHandler = AnonymousTypeHandler(this) + private val kdocCommentKeeper = KDocCommentKeeper(kaptContext) + private var done = false fun convert(): JavacList { @@ -180,6 +182,7 @@ class ClassFileToSourceStubConverter( val classes = JavacList.of(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?): Boolean { @@ -883,6 +886,11 @@ class ClassFileToSourceStubConverter( Type.DOUBLE_TYPE -> 0.0 else -> null } + + private fun T.keepComments(node: Any): T { + kdocCommentKeeper.saveKDocComment(this, node) + return this + } } private fun Any?.isOfPrimiviteType(): Boolean = when(this) { diff --git a/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/stubs/KDocCommentKeeper.kt b/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/stubs/KDocCommentKeeper.kt new file mode 100644 index 00000000000..ff113514d55 --- /dev/null +++ b/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/stubs/KDocCommentKeeper.kt @@ -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 () + 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() + + 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) + } +} \ No newline at end of file diff --git a/plugins/kapt3/kapt3-compiler/test/org/jetbrains/kotlin/kapt3/test/ClassFileToSourceStubConverterTestGenerated.java b/plugins/kapt3/kapt3-compiler/test/org/jetbrains/kotlin/kapt3/test/ClassFileToSourceStubConverterTestGenerated.java index f54dcc894ee..ff00af6cb97 100644 --- a/plugins/kapt3/kapt3-compiler/test/org/jetbrains/kotlin/kapt3/test/ClassFileToSourceStubConverterTestGenerated.java +++ b/plugins/kapt3/kapt3-compiler/test/org/jetbrains/kotlin/kapt3/test/ClassFileToSourceStubConverterTestGenerated.java @@ -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"); diff --git a/plugins/kapt3/kapt3-compiler/test/org/jetbrains/kotlin/kapt3/test/KotlinKapt3IntegrationTests.kt b/plugins/kapt3/kapt3-compiler/test/org/jetbrains/kotlin/kapt3/test/KotlinKapt3IntegrationTests.kt index 84730351aea..1b174f37ac5 100644 --- a/plugins/kapt3/kapt3-compiler/test/org/jetbrains/kotlin/kapt3/test/KotlinKapt3IntegrationTests.kt +++ b/plugins/kapt3/kapt3-compiler/test/org/jetbrains/kotlin/kapt3/test/KotlinKapt3IntegrationTests.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()) diff --git a/plugins/kapt3/kapt3-compiler/testData/converter/comments.kt b/plugins/kapt3/kapt3-compiler/testData/converter/comments.kt new file mode 100644 index 00000000000..89da2e6cb4d --- /dev/null +++ b/plugins/kapt3/kapt3-compiler/testData/converter/comments.kt @@ -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() {} +} \ No newline at end of file diff --git a/plugins/kapt3/kapt3-compiler/testData/converter/comments.txt b/plugins/kapt3/kapt3-compiler/testData/converter/comments.txt new file mode 100644 index 00000000000..8ef663eab50 --- /dev/null +++ b/plugins/kapt3/kapt3-compiler/testData/converter/comments.txt @@ -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 = "()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 = "()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 = "(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 = "(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 = "()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(); +} diff --git a/plugins/kapt3/kapt3-compiler/testData/kotlinRunner/Simple.it.txt b/plugins/kapt3/kapt3-compiler/testData/kotlinRunner/Simple.it.txt index ecd7e5725b6..d5287da7d2c 100644 --- a/plugins/kapt3/kapt3-compiler/testData/kotlinRunner/Simple.it.txt +++ b/plugins/kapt3/kapt3-compiler/testData/kotlinRunner/Simple.it.txt @@ -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("()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("()V") - public Simple() { - super(); - } -} diff --git a/plugins/kapt3/kapt3-compiler/testData/kotlinRunner/Simple.kt b/plugins/kapt3/kapt3-compiler/testData/kotlinRunner/Simple.kt index de73c3a379a..4aa8c9e1260 100644 --- a/plugins/kapt3/kapt3-compiler/testData/kotlinRunner/Simple.kt +++ b/plugins/kapt3/kapt3-compiler/testData/kotlinRunner/Simple.kt @@ -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 } diff --git a/plugins/kapt3/kapt3-compiler/testData/runner/Simple.java b/plugins/kapt3/kapt3-compiler/testData/runner/Simple.java index 3b49e5635b2..afff1f59102 100644 --- a/plugins/kapt3/kapt3-compiler/testData/runner/Simple.java +++ b/plugins/kapt3/kapt3-compiler/testData/runner/Simple.java @@ -1,5 +1,8 @@ package test; +/** + * KDoc comment. + */ class Simple { @MyAnnotation void myMethod() {