From bb395e564d4d5a60dcff3021077a8c8bfa12d10f Mon Sep 17 00:00:00 2001 From: Oleg Yukhnevich Date: Wed, 15 Nov 2023 16:20:56 +0200 Subject: [PATCH 1/2] Support `inner` modifier for java non-`static` classes --- .../analysis/java/parsers/DokkaPsiParser.kt | 14 ++ .../src/test/kotlin/model/JavaTest.kt | 188 ++++++++++++++++++ 2 files changed, 202 insertions(+) diff --git a/dokka-subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/DokkaPsiParser.kt b/dokka-subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/DokkaPsiParser.kt index b8dabe42b6..9cfa2bfef9 100644 --- a/dokka-subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/DokkaPsiParser.kt +++ b/dokka-subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/DokkaPsiParser.kt @@ -236,6 +236,18 @@ internal class DokkaPsiParser( val implementedInterfacesExtra = ImplementedInterfaces(ancestry.allImplementedInterfaces().toSourceSetDependent()) + // used only for class and enum + val innerModifierExtra = when { + // top level java classes - no `inner` + psi.containingClass == null -> null + // java `static class` = kotlin `class` + psi.hasModifier(JvmModifier.STATIC) -> null + // java `class` = kotlin `inner class` + else -> setOf( + ExtraModifiers.KotlinOnlyModifiers.Inner + ).toSourceSetDependent().toAdditionalModifiers() + } + when { isAnnotationType -> DAnnotation( @@ -295,6 +307,7 @@ internal class DokkaPsiParser( isExpectActual = false, extra = PropertyContainer.withAll( implementedInterfacesExtra, + innerModifierExtra, annotations.toList().toListOfAnnotations().toSourceSetDependent() .toAnnotations() ) @@ -341,6 +354,7 @@ internal class DokkaPsiParser( isExpectActual = false, extra = PropertyContainer.withAll( implementedInterfacesExtra, + innerModifierExtra, annotations.toList().toListOfAnnotations().toSourceSetDependent() .toAnnotations(), ancestry.exceptionInSupertypesOrNull() diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/model/JavaTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/model/JavaTest.kt index ff706c5e6f..8b21746598 100644 --- a/dokka-subprojects/plugin-base/src/test/kotlin/model/JavaTest.kt +++ b/dokka-subprojects/plugin-base/src/test/kotlin/model/JavaTest.kt @@ -6,6 +6,7 @@ package model import org.jetbrains.dokka.DokkaConfiguration import org.jetbrains.dokka.Platform +import org.jetbrains.dokka.base.signatures.KotlinSignatureUtils.modifiers import org.jetbrains.dokka.base.transformers.documentables.InheritorsInfo import org.jetbrains.dokka.links.* import org.jetbrains.dokka.model.* @@ -232,6 +233,193 @@ class JavaTest : AbstractModelTest("/src/main/kotlin/java/Test.java", "java") { } } + @Test + fun innerClassModifiers() = inlineModelTest( + """ + |public class JavaClass { + | public class InnerClass { } + | public static class NestedClass { } + | public enum InnerEnum {S} + | public static enum NestedEnum {S} + | public interface InnerInterface { } + | public static interface NestedInterface { } + | public @interface InnerAnnotation { } + | public static @interface NestedAnnotation { } + |} + """, + configuration = configuration + ) { + with((this / "java" / "JavaClass").cast()) { + assertTrue(modifiers().isEmpty()) + + with((this / "InnerClass").cast()) { + assertContains(modifiers().values.single(), ExtraModifiers.KotlinOnlyModifiers.Inner) + } + with((this / "NestedClass").cast()) { + assertTrue(modifiers().isEmpty()) + } + with((this / "InnerEnum").cast()) { + assertTrue(modifiers().isEmpty()) + } + with((this / "NestedEnum").cast()) { + assertTrue(modifiers().isEmpty()) + } + with((this / "InnerInterface").cast()) { + assertTrue(modifiers().isEmpty()) + } + with((this / "NestedInterface").cast()) { + assertTrue(modifiers().isEmpty()) + } + with((this / "InnerAnnotation").cast()) { + assertTrue(modifiers().isEmpty()) + } + with((this / "NestedAnnotation").cast()) { + assertTrue(modifiers().isEmpty()) + } + } + } + + @Test + fun innerEnumModifiers() = inlineModelTest( + """ + |public enum JavaEnum { S; + | public class InnerClass { } + | public static class NestedClass { } + | public enum InnerEnum {S} + | public static enum NestedEnum {S} + | public interface InnerInterface { } + | public static interface NestedInterface { } + | public @interface InnerAnnotation { } + | public static @interface NestedAnnotation { } + |} + """, + configuration = configuration + ) { + // `enum` behaves the same as `class` regarding `inner` + with((this / "java" / "JavaEnum").cast()) { + assertTrue(modifiers().isEmpty()) + + with((this / "InnerClass").cast()) { + assertContains(modifiers().values.single(), ExtraModifiers.KotlinOnlyModifiers.Inner) + } + with((this / "NestedClass").cast()) { + assertTrue(modifiers().isEmpty()) + } + with((this / "InnerEnum").cast()) { + assertTrue(modifiers().isEmpty()) + } + with((this / "NestedEnum").cast()) { + assertTrue(modifiers().isEmpty()) + } + with((this / "InnerInterface").cast()) { + assertTrue(modifiers().isEmpty()) + } + with((this / "NestedInterface").cast()) { + assertTrue(modifiers().isEmpty()) + } + with((this / "InnerAnnotation").cast()) { + assertTrue(modifiers().isEmpty()) + } + with((this / "NestedAnnotation").cast()) { + assertTrue(modifiers().isEmpty()) + } + } + } + + @Test + fun innerInterfaceModifiers() = inlineModelTest( + """ + |public interface JavaInterface { + | public class InnerClass { } + | public static class NestedClass { } + | public enum InnerEnum {S} + | public static enum NestedEnum {S} + | public interface InnerInterface { } + | public static interface NestedInterface { } + | public @interface InnerAnnotation { } + | public static @interface NestedAnnotation { } + |} + """, + configuration = configuration + ) { + // new types nested in `interface` are `static` by default (no `inner` modifier) + with((this / "java" / "JavaInterface").cast()) { + assertTrue(modifiers().isEmpty()) + + with((this / "InnerClass").cast()) { + assertTrue(modifiers().isEmpty()) + } + with((this / "NestedClass").cast()) { + assertTrue(modifiers().isEmpty()) + } + with((this / "InnerEnum").cast()) { + assertTrue(modifiers().isEmpty()) + } + with((this / "NestedEnum").cast()) { + assertTrue(modifiers().isEmpty()) + } + with((this / "InnerInterface").cast()) { + assertTrue(modifiers().isEmpty()) + } + with((this / "NestedInterface").cast()) { + assertTrue(modifiers().isEmpty()) + } + with((this / "InnerAnnotation").cast()) { + assertTrue(modifiers().isEmpty()) + } + with((this / "NestedAnnotation").cast()) { + assertTrue(modifiers().isEmpty()) + } + } + } + + @Test + fun innerAnnotationModifiers() = inlineModelTest( + """ + |public @interface JavaInterface { + | public class InnerClass { } + | public static class NestedClass { } + | public enum InnerEnum {S} + | public static enum NestedEnum {S} + | public interface InnerInterface { } + | public static interface NestedInterface { } + | public @interface InnerAnnotation { } + | public static @interface NestedAnnotation { } + |} + """, + configuration = configuration + ) { + // `@interface` behaves the same as `interface` regarding `inner` + with((this / "java" / "JavaInterface").cast()) { + assertTrue(modifiers().isEmpty()) + + with((this / "InnerClass").cast()) { + assertTrue(modifiers().isEmpty()) + } + with((this / "NestedClass").cast()) { + assertTrue(modifiers().isEmpty()) + } + with((this / "InnerEnum").cast()) { + assertTrue(modifiers().isEmpty()) + } + with((this / "NestedEnum").cast()) { + assertTrue(modifiers().isEmpty()) + } + with((this / "InnerInterface").cast()) { + assertTrue(modifiers().isEmpty()) + } + with((this / "NestedInterface").cast()) { + assertTrue(modifiers().isEmpty()) + } + with((this / "InnerAnnotation").cast()) { + assertTrue(modifiers().isEmpty()) + } + with((this / "NestedAnnotation").cast()) { + assertTrue(modifiers().isEmpty()) + } + } + } + @Test fun varargs() { inlineModelTest( From 26259be2682f3cb1fe899bc5a8173f69e3097258 Mon Sep 17 00:00:00 2001 From: Oleg Yukhnevich Date: Mon, 20 Nov 2023 18:12:39 +0200 Subject: [PATCH 2/2] Split and move tests to analysis module --- .../jvm/java/InnerModifierJavaAnalysisTest.kt | 293 ++++++++++++++++++ .../src/test/kotlin/model/JavaTest.kt | 188 ----------- 2 files changed, 293 insertions(+), 188 deletions(-) create mode 100644 dokka-subprojects/analysis-kotlin-api/src/test/kotlin/org/jetbrains/dokka/analysis/test/jvm/java/InnerModifierJavaAnalysisTest.kt diff --git a/dokka-subprojects/analysis-kotlin-api/src/test/kotlin/org/jetbrains/dokka/analysis/test/jvm/java/InnerModifierJavaAnalysisTest.kt b/dokka-subprojects/analysis-kotlin-api/src/test/kotlin/org/jetbrains/dokka/analysis/test/jvm/java/InnerModifierJavaAnalysisTest.kt new file mode 100644 index 0000000000..a4561b7b4d --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/test/kotlin/org/jetbrains/dokka/analysis/test/jvm/java/InnerModifierJavaAnalysisTest.kt @@ -0,0 +1,293 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.test.jvm.java + +import org.jetbrains.dokka.analysis.test.api.javaTestProject +import org.jetbrains.dokka.analysis.test.api.parse +import org.jetbrains.dokka.model.* +import org.jetbrains.dokka.model.properties.WithExtraProperties +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class InnerModifierJavaAnalysisTest { + + @Test + fun `top level java declarations should not have kotlin inner modifier`() { + val testProject = javaTestProject { + javaFile(pathFromSrc = "JavaClass.java") { + +"public class JavaClass { }" + } + javaFile(pathFromSrc = "JavaEnum.java") { + +"public enum JavaEnum { S; }" + } + javaFile(pathFromSrc = "JavaInterface.java") { + +"public interface JavaInterface { }" + } + javaFile(pathFromSrc = "JavaAnnotation.java") { + +"public @interface JavaAnnotation { }" + } + } + + val module = testProject.parse() + val pkg = module.packages.single() + + assertEquals(4, pkg.classlikes.size) + + pkg.classlikes.forEach { + @Suppress("UNCHECKED_CAST") + assertTrue((it as WithExtraProperties).kotlinOnlyModifiers().isEmpty()) + } + } + + @Test + fun `java declarations nested inside interfaces should not have kotlin inner modifier`() { + val testProject = javaTestProject { + javaFile(pathFromSrc = "JavaInterface.java") { + +""" + public interface JavaInterface { + public class InnerClass { } + public static class NestedClass { } + public enum InnerEnum {S} + public static enum NestedEnum {S} + public interface InnerInterface { } + public static interface NestedInterface { } + public @interface InnerAnnotation { } + public static @interface NestedAnnotation { } + } + """ + } + } + + val module = testProject.parse() + val pkg = module.packages.single() + val javaInterface = pkg.classlikes.single() + + assertTrue(javaInterface is DInterface) + assertEquals(8, javaInterface.classlikes.size) + + javaInterface.classlikes.forEach { + @Suppress("UNCHECKED_CAST") + assertTrue((it as WithExtraProperties).kotlinOnlyModifiers().isEmpty()) + } + } + + @Test + fun `java declarations nested inside annotation interface should not have kotlin inner modifier`() { + val testProject = javaTestProject { + javaFile(pathFromSrc = "JavaAnnotation.java") { + +""" + public @interface JavaAnnotation { + public class InnerClass { } + public static class NestedClass { } + public enum InnerEnum {S} + public static enum NestedEnum {S} + public interface InnerInterface { } + public static interface NestedInterface { } + public @interface InnerAnnotation { } + public static @interface NestedAnnotation { } + } + """ + } + } + + val module = testProject.parse() + val pkg = module.packages.single() + val javaAnnotation = pkg.classlikes.single() + + assertTrue(javaAnnotation is DAnnotation) + assertEquals(8, javaAnnotation.classlikes.size) + + javaAnnotation.classlikes.forEach { + @Suppress("UNCHECKED_CAST") + assertTrue((it as WithExtraProperties).kotlinOnlyModifiers().isEmpty()) + } + } + + // java classes tests + + @Test + fun `java class nested inside class should have kotlin inner modifiers`() { + val testProject = javaTestProject { + javaFile(pathFromSrc = "JavaClass.java") { + +""" + public class JavaClass { + public class NestedClass { } + } + """ + } + } + + val module = testProject.parse() + val pkg = module.packages.single() + val javaClass = pkg.classlikes.single() + + assertTrue(javaClass is DClass) + assertEquals("JavaClass", javaClass.name) + + val nestedClass = javaClass.classlikes.single() + + assertTrue(nestedClass is DClass) + assertEquals("NestedClass", nestedClass.name) + + assertEquals( + setOf(ExtraModifiers.KotlinOnlyModifiers.Inner), + nestedClass.kotlinOnlyModifiers().values.single() + ) + } + + @Test + fun `static java class nested inside class should not have kotlin inner modifiers`() { + val testProject = javaTestProject { + javaFile(pathFromSrc = "JavaClass.java") { + +""" + public class JavaClass { + public static class NestedClass { } + } + """ + } + } + + val module = testProject.parse() + val pkg = module.packages.single() + val javaClass = pkg.classlikes.single() + + assertTrue(javaClass is DClass) + assertEquals("JavaClass", javaClass.name) + + val nestedClass = javaClass.classlikes.single() + + assertTrue(nestedClass is DClass) + assertEquals("NestedClass", nestedClass.name) + + assertTrue(nestedClass.kotlinOnlyModifiers().isEmpty()) + } + + @Test + fun `java non-classes nested inside class should not have kotlin inner modifier`() { + val testProject = javaTestProject { + javaFile(pathFromSrc = "JavaClass.java") { + +""" + public class JavaClass { + public enum InnerEnum {S} + public static enum NestedEnum {S} + public interface InnerInterface { } + public static interface NestedInterface { } + public @interface InnerAnnotation { } + public static @interface NestedAnnotation { } + } + """ + } + } + + val module = testProject.parse() + val pkg = module.packages.single() + val javaClass = pkg.classlikes.single() + + assertTrue(javaClass is DClass) + assertEquals(6, javaClass.classlikes.size) + + javaClass.classlikes.forEach { + @Suppress("UNCHECKED_CAST") + assertTrue((it as WithExtraProperties).kotlinOnlyModifiers().isEmpty()) + } + } + + // java enums tests + + @Test + fun `static java class nested inside enum should not have kotlin inner modifiers`() { + val testProject = javaTestProject { + javaFile(pathFromSrc = "JavaEnum.java") { + +""" + public enum JavaEnum { S; + public static class NestedClass { } + } + """ + } + } + + val module = testProject.parse() + val pkg = module.packages.single() + val javaEnum = pkg.classlikes.single() + + assertTrue(javaEnum is DEnum) + assertEquals("JavaEnum", javaEnum.name) + + val nestedClass = javaEnum.classlikes.single() + + assertTrue(nestedClass is DClass) + assertEquals("NestedClass", nestedClass.name) + + assertTrue(nestedClass.kotlinOnlyModifiers().isEmpty()) + } + + @Test + fun `java class nested inside enum should have kotlin inner modifiers`() { + val testProject = javaTestProject { + javaFile(pathFromSrc = "JavaEnum.java") { + +""" + public enum JavaEnum { S; + public class NestedClass { } + } + """ + } + } + + val module = testProject.parse() + val pkg = module.packages.single() + val javaEnum = pkg.classlikes.single() + + assertTrue(javaEnum is DEnum) + assertEquals("JavaEnum", javaEnum.name) + + val nestedClass = javaEnum.classlikes.single() + + assertTrue(nestedClass is DClass) + assertEquals("NestedClass", nestedClass.name) + + assertEquals( + setOf(ExtraModifiers.KotlinOnlyModifiers.Inner), + nestedClass.kotlinOnlyModifiers().values.single() + ) + } + + @Test + fun `java non-classes nested inside enum should not have kotlin inner modifier`() { + val testProject = javaTestProject { + javaFile(pathFromSrc = "JavaEnum.java") { + +""" + public enum JavaEnum { S; + public enum InnerEnum {S} + public static enum NestedEnum {S} + public interface InnerInterface { } + public static interface NestedInterface { } + public @interface InnerAnnotation { } + public static @interface NestedAnnotation { } + } + """ + } + } + + val module = testProject.parse() + val pkg = module.packages.single() + val javaEnum = pkg.classlikes.single() + + assertTrue(javaEnum is DEnum) + assertEquals(6, javaEnum.classlikes.size) + + javaEnum.classlikes.forEach { + @Suppress("UNCHECKED_CAST") + assertTrue((it as WithExtraProperties).kotlinOnlyModifiers().isEmpty()) + } + } + + // copied from org.jetbrains.dokka.base.signatures.KotlinSignatureUtils + private fun WithExtraProperties.kotlinOnlyModifiers(): SourceSetDependent> { + return extra[AdditionalModifiers]?.content?.entries?.associate { + it.key to it.value.filterIsInstance().toSet() + } ?: emptyMap() + } +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/model/JavaTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/model/JavaTest.kt index 8b21746598..ff706c5e6f 100644 --- a/dokka-subprojects/plugin-base/src/test/kotlin/model/JavaTest.kt +++ b/dokka-subprojects/plugin-base/src/test/kotlin/model/JavaTest.kt @@ -6,7 +6,6 @@ package model import org.jetbrains.dokka.DokkaConfiguration import org.jetbrains.dokka.Platform -import org.jetbrains.dokka.base.signatures.KotlinSignatureUtils.modifiers import org.jetbrains.dokka.base.transformers.documentables.InheritorsInfo import org.jetbrains.dokka.links.* import org.jetbrains.dokka.model.* @@ -233,193 +232,6 @@ class JavaTest : AbstractModelTest("/src/main/kotlin/java/Test.java", "java") { } } - @Test - fun innerClassModifiers() = inlineModelTest( - """ - |public class JavaClass { - | public class InnerClass { } - | public static class NestedClass { } - | public enum InnerEnum {S} - | public static enum NestedEnum {S} - | public interface InnerInterface { } - | public static interface NestedInterface { } - | public @interface InnerAnnotation { } - | public static @interface NestedAnnotation { } - |} - """, - configuration = configuration - ) { - with((this / "java" / "JavaClass").cast()) { - assertTrue(modifiers().isEmpty()) - - with((this / "InnerClass").cast()) { - assertContains(modifiers().values.single(), ExtraModifiers.KotlinOnlyModifiers.Inner) - } - with((this / "NestedClass").cast()) { - assertTrue(modifiers().isEmpty()) - } - with((this / "InnerEnum").cast()) { - assertTrue(modifiers().isEmpty()) - } - with((this / "NestedEnum").cast()) { - assertTrue(modifiers().isEmpty()) - } - with((this / "InnerInterface").cast()) { - assertTrue(modifiers().isEmpty()) - } - with((this / "NestedInterface").cast()) { - assertTrue(modifiers().isEmpty()) - } - with((this / "InnerAnnotation").cast()) { - assertTrue(modifiers().isEmpty()) - } - with((this / "NestedAnnotation").cast()) { - assertTrue(modifiers().isEmpty()) - } - } - } - - @Test - fun innerEnumModifiers() = inlineModelTest( - """ - |public enum JavaEnum { S; - | public class InnerClass { } - | public static class NestedClass { } - | public enum InnerEnum {S} - | public static enum NestedEnum {S} - | public interface InnerInterface { } - | public static interface NestedInterface { } - | public @interface InnerAnnotation { } - | public static @interface NestedAnnotation { } - |} - """, - configuration = configuration - ) { - // `enum` behaves the same as `class` regarding `inner` - with((this / "java" / "JavaEnum").cast()) { - assertTrue(modifiers().isEmpty()) - - with((this / "InnerClass").cast()) { - assertContains(modifiers().values.single(), ExtraModifiers.KotlinOnlyModifiers.Inner) - } - with((this / "NestedClass").cast()) { - assertTrue(modifiers().isEmpty()) - } - with((this / "InnerEnum").cast()) { - assertTrue(modifiers().isEmpty()) - } - with((this / "NestedEnum").cast()) { - assertTrue(modifiers().isEmpty()) - } - with((this / "InnerInterface").cast()) { - assertTrue(modifiers().isEmpty()) - } - with((this / "NestedInterface").cast()) { - assertTrue(modifiers().isEmpty()) - } - with((this / "InnerAnnotation").cast()) { - assertTrue(modifiers().isEmpty()) - } - with((this / "NestedAnnotation").cast()) { - assertTrue(modifiers().isEmpty()) - } - } - } - - @Test - fun innerInterfaceModifiers() = inlineModelTest( - """ - |public interface JavaInterface { - | public class InnerClass { } - | public static class NestedClass { } - | public enum InnerEnum {S} - | public static enum NestedEnum {S} - | public interface InnerInterface { } - | public static interface NestedInterface { } - | public @interface InnerAnnotation { } - | public static @interface NestedAnnotation { } - |} - """, - configuration = configuration - ) { - // new types nested in `interface` are `static` by default (no `inner` modifier) - with((this / "java" / "JavaInterface").cast()) { - assertTrue(modifiers().isEmpty()) - - with((this / "InnerClass").cast()) { - assertTrue(modifiers().isEmpty()) - } - with((this / "NestedClass").cast()) { - assertTrue(modifiers().isEmpty()) - } - with((this / "InnerEnum").cast()) { - assertTrue(modifiers().isEmpty()) - } - with((this / "NestedEnum").cast()) { - assertTrue(modifiers().isEmpty()) - } - with((this / "InnerInterface").cast()) { - assertTrue(modifiers().isEmpty()) - } - with((this / "NestedInterface").cast()) { - assertTrue(modifiers().isEmpty()) - } - with((this / "InnerAnnotation").cast()) { - assertTrue(modifiers().isEmpty()) - } - with((this / "NestedAnnotation").cast()) { - assertTrue(modifiers().isEmpty()) - } - } - } - - @Test - fun innerAnnotationModifiers() = inlineModelTest( - """ - |public @interface JavaInterface { - | public class InnerClass { } - | public static class NestedClass { } - | public enum InnerEnum {S} - | public static enum NestedEnum {S} - | public interface InnerInterface { } - | public static interface NestedInterface { } - | public @interface InnerAnnotation { } - | public static @interface NestedAnnotation { } - |} - """, - configuration = configuration - ) { - // `@interface` behaves the same as `interface` regarding `inner` - with((this / "java" / "JavaInterface").cast()) { - assertTrue(modifiers().isEmpty()) - - with((this / "InnerClass").cast()) { - assertTrue(modifiers().isEmpty()) - } - with((this / "NestedClass").cast()) { - assertTrue(modifiers().isEmpty()) - } - with((this / "InnerEnum").cast()) { - assertTrue(modifiers().isEmpty()) - } - with((this / "NestedEnum").cast()) { - assertTrue(modifiers().isEmpty()) - } - with((this / "InnerInterface").cast()) { - assertTrue(modifiers().isEmpty()) - } - with((this / "NestedInterface").cast()) { - assertTrue(modifiers().isEmpty()) - } - with((this / "InnerAnnotation").cast()) { - assertTrue(modifiers().isEmpty()) - } - with((this / "NestedAnnotation").cast()) { - assertTrue(modifiers().isEmpty()) - } - } - } - @Test fun varargs() { inlineModelTest(