From 706c0d2f0e732667b83905c4af1d96efd93f9897 Mon Sep 17 00:00:00 2001 From: Marcin Aman Date: Tue, 15 Dec 2020 16:22:10 +0100 Subject: [PATCH] Parsing of JvmName --- .../src/main/kotlin/model/additionalExtras.kt | 19 +++++++++++-------- .../kotlin/translators/annotationsValue.kt | 3 +++ ...faultDescriptorToDocumentableTranslator.kt | 16 +++++----------- .../psi/DefaultPsiToDocumentableTranslator.kt | 4 ++++ .../annotations/ContentForAnnotationsTest.kt | 7 +++---- .../annotations/FileLevelJvmNameTest.kt | 16 ++++++++-------- .../base/src/test/kotlin/model/ClassesTest.kt | 6 +++--- .../src/test/kotlin/model/FunctionsTest.kt | 4 ++-- .../src/test/kotlin/model/PropertyTest.kt | 2 +- .../test/kotlin/signatures/SignatureTest.kt | 12 ++++-------- .../JavadocClasslikeTemplateMapTest.kt | 4 ++-- .../converters/KotlinToJavaConverter.kt | 8 ++++---- .../kotlin/transformers/JvmNameProvider.kt | 16 ++++++++++++---- .../src/test/kotlin/KotlinAsJavaPluginTest.kt | 4 ++-- 14 files changed, 64 insertions(+), 57 deletions(-) create mode 100644 plugins/base/src/main/kotlin/translators/annotationsValue.kt diff --git a/core/src/main/kotlin/model/additionalExtras.kt b/core/src/main/kotlin/model/additionalExtras.kt index 0a59ec3617..2308b6418f 100644 --- a/core/src/main/kotlin/model/additionalExtras.kt +++ b/core/src/main/kotlin/model/additionalExtras.kt @@ -21,13 +21,12 @@ class AdditionalModifiers(val content: SourceSetDependent>) fun SourceSetDependent>.toAdditionalModifiers() = AdditionalModifiers(this) -class Annotations( - @Deprecated("Use directAnnotations or fileLevelAnnotations") - val content: SourceSetDependent> +data class Annotations( + private val myContent: SourceSetDependent> ) : ExtraProperty { companion object : ExtraProperty.Key { override fun mergeStrategyFor(left: Annotations, right: Annotations): MergeStrategy = - MergeStrategy.Replace(Annotations(left.content + right.content)) + MergeStrategy.Replace(Annotations(left.myContent + right.myContent)) } override val key: ExtraProperty.Key = Annotations @@ -46,20 +45,24 @@ class Annotations( override fun hashCode(): Int = dri.hashCode() } + @Deprecated("Use directAnnotations or fileLevelAnnotations") + val content: SourceSetDependent> + get() = myContent + val directAnnotations: SourceSetDependent> = annotationsByScope(AnnotationScope.DIRECT) val fileLevelAnnotations: SourceSetDependent> = annotationsByScope(AnnotationScope.FILE) private fun annotationsByScope(scope: AnnotationScope): SourceSetDependent> = - content.entries.mapNotNull { (key, value) -> + myContent.entries.mapNotNull { (key, value) -> val withoutFileLevel = value.filter { it.scope == scope } if (withoutFileLevel.isEmpty()) null else Pair(key, withoutFileLevel) }.toMap() -} -enum class AnnotationScope { - DIRECT, FILE + enum class AnnotationScope { + DIRECT, FILE + } } fun SourceSetDependent>.toAnnotations() = Annotations(this) diff --git a/plugins/base/src/main/kotlin/translators/annotationsValue.kt b/plugins/base/src/main/kotlin/translators/annotationsValue.kt new file mode 100644 index 0000000000..a840816a5b --- /dev/null +++ b/plugins/base/src/main/kotlin/translators/annotationsValue.kt @@ -0,0 +1,3 @@ +package org.jetbrains.dokka.base.translators + +internal fun unquotedValue(value: String): String = value.removeSurrounding("\"") \ No newline at end of file diff --git a/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt b/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt index 46b61f1acb..f9a72f5936 100644 --- a/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt +++ b/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt @@ -10,6 +10,7 @@ import org.jetbrains.dokka.analysis.from import org.jetbrains.dokka.base.DokkaBase import org.jetbrains.dokka.base.parsers.MarkdownParser import org.jetbrains.dokka.base.translators.isDirectlyAnException +import org.jetbrains.dokka.base.translators.unquotedValue import org.jetbrains.dokka.links.* import org.jetbrains.dokka.links.Callable import org.jetbrains.dokka.model.* @@ -588,7 +589,7 @@ private class DokkaDescriptorVisitor( val name = run { val modifier = if (isGetter) "get" else "set" val rawName = propertyDescriptor.name.asString() - "$modifier${rawName[0].toUpperCase()}${rawName.drop(1)}" + "$modifier${rawName.capitalize()}" } val parameters = @@ -782,8 +783,7 @@ private class DokkaDescriptorVisitor( private suspend fun org.jetbrains.kotlin.descriptors.annotations.Annotations.getPresentableName(): String? = map { it.toAnnotation() }.singleOrNull { it.dri.classNames == "ParameterName" }?.params?.get("name") - .safeAs()?.value?.drop(1) - ?.dropLast(1) // Dropping enclosing doublequotes because we don't want to have it in our custom signature serializer + .safeAs()?.value?.let { unquotedValue(it) } private suspend fun KotlinType.toBound(): Bound = when (this) { @@ -931,13 +931,7 @@ private class DokkaDescriptorVisitor( else -> StringValue(unquotedValue(toString())) } - private fun unquotedValue(value: String): String = if (value.startsWith('"') && value.endsWith('"')) { - if (value.length == 2) "" else value.substring(1, value.lastIndex) - } else { - value - } - - private suspend fun AnnotationDescriptor.toAnnotation(scope: AnnotationScope = AnnotationScope.DIRECT): Annotations.Annotation { + private suspend fun AnnotationDescriptor.toAnnotation(scope: Annotations.AnnotationScope = Annotations.AnnotationScope.DIRECT): Annotations.Annotation { val dri = DRI.from(annotationClass as DeclarationDescriptor) return Annotations.Annotation( DRI.from(annotationClass as DeclarationDescriptor), @@ -1023,7 +1017,7 @@ private class DokkaDescriptorVisitor( ?.let { file -> resolutionFacade.resolveSession.getFileAnnotations(file) } ?.toList() .orEmpty() - .parallelMap { it.toAnnotation(scope = AnnotationScope.FILE) } + .parallelMap { it.toAnnotation(scope = Annotations.AnnotationScope.FILE) } } private data class AncestryLevel( diff --git a/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt b/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt index 9a1b05d5d7..cd51e9dd81 100644 --- a/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt +++ b/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt @@ -17,6 +17,7 @@ import org.jetbrains.dokka.base.DokkaBase import org.jetbrains.dokka.base.translators.isDirectlyAnException import org.jetbrains.dokka.base.translators.psi.parsers.JavaDocumentationParser import org.jetbrains.dokka.base.translators.psi.parsers.JavadocParser +import org.jetbrains.dokka.base.translators.unquotedValue import org.jetbrains.dokka.links.DRI import org.jetbrains.dokka.links.nextTarget import org.jetbrains.dokka.links.withClass @@ -530,6 +531,9 @@ class DefaultPsiToDocumentableTranslator( private fun JvmAnnotationAttribute.toValue(): AnnotationParameterValue = when (this) { is PsiNameValuePair -> value?.toValue() ?: StringValue("") else -> StringValue(this.attributeName) + }.let { annotationValue -> + if (annotationValue is StringValue) annotationValue.copy(unquotedValue(annotationValue.value)) + else annotationValue } private fun PsiAnnotationMemberValue.toValue(): AnnotationParameterValue? = when (this) { diff --git a/plugins/base/src/test/kotlin/content/annotations/ContentForAnnotationsTest.kt b/plugins/base/src/test/kotlin/content/annotations/ContentForAnnotationsTest.kt index 815e28f157..d88d95059c 100644 --- a/plugins/base/src/test/kotlin/content/annotations/ContentForAnnotationsTest.kt +++ b/plugins/base/src/test/kotlin/content/annotations/ContentForAnnotationsTest.kt @@ -5,7 +5,6 @@ import org.jetbrains.dokka.pages.ContentPage import org.jetbrains.dokka.pages.PackagePageNode import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest import org.jetbrains.dokka.links.DRI -import org.jetbrains.dokka.model.AnnotationScope import org.jetbrains.dokka.model.Annotations import org.jetbrains.dokka.model.StringValue import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult @@ -243,7 +242,7 @@ class ContentForAnnotationsTest : BaseAbstractTest() { fun expectedAnnotation(name: String) = Annotations.Annotation( dri = DRI("kotlin.jvm", "JvmName"), params = mapOf("name" to StringValue(name)), - scope = AnnotationScope.DIRECT, + scope = Annotations.AnnotationScope.DIRECT, mustBeDocumented = false ) @@ -257,11 +256,11 @@ class ContentForAnnotationsTest : BaseAbstractTest() { assertEquals(expectedAnnotation("xd"), getterAnnotation) assertFalse(getterAnnotation?.mustBeDocumented!!) - assertEquals(AnnotationScope.DIRECT, getterAnnotation.scope) + assertEquals(Annotations.AnnotationScope.DIRECT, getterAnnotation.scope) assertEquals(expectedAnnotation("asd"), setterAnnotation) assertFalse(setterAnnotation?.mustBeDocumented!!) - assertEquals(AnnotationScope.DIRECT, setterAnnotation.scope) + assertEquals(Annotations.AnnotationScope.DIRECT, setterAnnotation.scope) } } } diff --git a/plugins/base/src/test/kotlin/content/annotations/FileLevelJvmNameTest.kt b/plugins/base/src/test/kotlin/content/annotations/FileLevelJvmNameTest.kt index f4b2ef54a4..f2fd518c31 100644 --- a/plugins/base/src/test/kotlin/content/annotations/FileLevelJvmNameTest.kt +++ b/plugins/base/src/test/kotlin/content/annotations/FileLevelJvmNameTest.kt @@ -2,10 +2,8 @@ package content.annotations import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest import org.jetbrains.dokka.links.DRI -import org.jetbrains.dokka.model.AnnotationScope import org.jetbrains.dokka.model.Annotations import org.jetbrains.dokka.model.StringValue -import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.ValueSource import kotlin.test.assertEquals @@ -67,14 +65,15 @@ class FileLevelJvmNameTest : BaseAbstractTest() { @ParameterizedTest @ValueSource(strings = [functionTest, extensionFunctionTest]) - fun `jvm name should be included in functions extra`(query: String){ + fun `jvm name should be included in functions extra`(query: String) { testInline( - query.trimIndent(), testConfiguration) { + query.trimIndent(), testConfiguration + ) { documentablesCreationStage = { modules -> val expectedAnnotation = Annotations.Annotation( dri = DRI("kotlin.jvm", "JvmName"), params = mapOf("name" to StringValue("CustomJvmName")), - scope = AnnotationScope.FILE, + scope = Annotations.AnnotationScope.FILE, mustBeDocumented = false ) val function = modules.flatMap { it.packages }.first().functions.first() @@ -89,14 +88,15 @@ class FileLevelJvmNameTest : BaseAbstractTest() { @ParameterizedTest @ValueSource(strings = [propertyTest, extensionPropertyTest]) - fun `jvm name should be included in properties extra`(query: String){ + fun `jvm name should be included in properties extra`(query: String) { testInline( - query.trimIndent(), testConfiguration) { + query.trimIndent(), testConfiguration + ) { documentablesCreationStage = { modules -> val expectedAnnotation = Annotations.Annotation( dri = DRI("kotlin.jvm", "JvmName"), params = mapOf("name" to StringValue("CustomJvmName")), - scope = AnnotationScope.FILE, + scope = Annotations.AnnotationScope.FILE, mustBeDocumented = false ) val properties = modules.flatMap { it.packages }.first().properties.first() diff --git a/plugins/base/src/test/kotlin/model/ClassesTest.kt b/plugins/base/src/test/kotlin/model/ClassesTest.kt index ef912e707e..2260a46f68 100644 --- a/plugins/base/src/test/kotlin/model/ClassesTest.kt +++ b/plugins/base/src/test/kotlin/model/ClassesTest.kt @@ -187,7 +187,7 @@ class ClassesTest : AbstractModelTest("/src/main/kotlin/classes/Test.kt", "class with(first()) { dri.classNames equals "Deprecated" params.entries counts 1 - (params["message"].assertNotNull("message") as StringValue).value equals "\"should no longer be used\"" + (params["message"].assertNotNull("message") as StringValue).value equals "should no longer be used" } } } @@ -366,7 +366,7 @@ class ClassesTest : AbstractModelTest("/src/main/kotlin/classes/Test.kt", "class with(first()) { dri.classNames equals "SinceKotlin" params.entries counts 1 - (params["version"].assertNotNull("version") as StringValue).value equals "\"1.1\"" + (params["version"].assertNotNull("version") as StringValue).value equals "1.1" } } } @@ -428,7 +428,7 @@ class ClassesTest : AbstractModelTest("/src/main/kotlin/classes/Test.kt", "class with((this / "classes" / "Foo").cast()) { with(extra[Annotations]?.directAnnotations?.values?.firstOrNull()?.firstOrNull().assertNotNull("annotations")) { dri.toString() equals "kotlin/Suppress///PointingToDeclaration/" - (params["names"].assertNotNull("param") as ArrayValue).value equals listOf(StringValue("\"abc\"")) + (params["names"].assertNotNull("param") as ArrayValue).value equals listOf(StringValue("abc")) } } } diff --git a/plugins/base/src/test/kotlin/model/FunctionsTest.kt b/plugins/base/src/test/kotlin/model/FunctionsTest.kt index 84e6c866e6..ac9b6d7d99 100644 --- a/plugins/base/src/test/kotlin/model/FunctionsTest.kt +++ b/plugins/base/src/test/kotlin/model/FunctionsTest.kt @@ -146,7 +146,7 @@ class FunctionTest : AbstractModelTest("/src/main/kotlin/function/Test.kt", "fun with(first()) { dri.classNames equals "Suppress" params.entries counts 1 - (params["names"].assertNotNull("param") as ArrayValue).value equals listOf(StringValue("\"FOO\"")) + (params["names"].assertNotNull("param") as ArrayValue).value equals listOf(StringValue("FOO")) } } } @@ -386,7 +386,7 @@ class FunctionTest : AbstractModelTest("/src/main/kotlin/function/Test.kt", "fun with(first()) { dri.classNames equals "SinceKotlin" params.entries counts 1 - (params["version"].assertNotNull("version") as StringValue).value equals "\"1.1\"" + (params["version"].assertNotNull("version") as StringValue).value equals "1.1" } } } diff --git a/plugins/base/src/test/kotlin/model/PropertyTest.kt b/plugins/base/src/test/kotlin/model/PropertyTest.kt index 287f0c3e8d..8474116fcf 100644 --- a/plugins/base/src/test/kotlin/model/PropertyTest.kt +++ b/plugins/base/src/test/kotlin/model/PropertyTest.kt @@ -155,7 +155,7 @@ class PropertyTest : AbstractModelTest("/src/main/kotlin/property/Test.kt", "pro with(first()) { dri.classNames equals "SinceKotlin" params.entries counts 1 - (params["version"].assertNotNull("version") as StringValue).value equals "\"1.1\"" + (params["version"].assertNotNull("version") as StringValue).value equals "1.1" } } } diff --git a/plugins/base/src/test/kotlin/signatures/SignatureTest.kt b/plugins/base/src/test/kotlin/signatures/SignatureTest.kt index ba4b631fda..f9d95f46fd 100644 --- a/plugins/base/src/test/kotlin/signatures/SignatureTest.kt +++ b/plugins/base/src/test/kotlin/signatures/SignatureTest.kt @@ -1,11 +1,7 @@ package signatures -import org.jetbrains.dokka.DokkaConfiguration import org.jetbrains.dokka.DokkaSourceSetID -import org.jetbrains.dokka.jdk import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest -import org.jsoup.Jsoup -import org.jsoup.nodes.Element import org.junit.jupiter.api.Test import utils.* @@ -295,7 +291,7 @@ class SignatureTest : BaseAbstractTest() { .firstSignature() .match( Div( - Div("@", A("Marking"), "(", Span("msg = ", Span("\"Nenya\"")), Wbr, ")"), + Div("@", A("Marking"), "(", Span("msg = ", Span("Nenya")), Wbr, ")"), Div("@", A("Marking2"), "(", Span("int = ", Span("1")), Wbr, ")") ), "fun ", A("simpleFun"), @@ -332,9 +328,9 @@ class SignatureTest : BaseAbstractTest() { Div( "@", A("Marking"), "(", Span( "msg = [", - Span(Span("\"Nenya\""), ", "), Wbr, - Span(Span("\"Vilya\""), ", "), Wbr, - Span(Span("\"Narya\"")), Wbr, "]" + Span(Span("Nenya"), ", "), Wbr, + Span(Span("Vilya"), ", "), Wbr, + Span(Span("Narya")), Wbr, "]" ), Wbr, ")" ) ), diff --git a/plugins/javadoc/src/test/kotlin/org/jetbrains/dokka/javadoc/JavadocClasslikeTemplateMapTest.kt b/plugins/javadoc/src/test/kotlin/org/jetbrains/dokka/javadoc/JavadocClasslikeTemplateMapTest.kt index 2fb1ae0e31..6535a22d6a 100644 --- a/plugins/javadoc/src/test/kotlin/org/jetbrains/dokka/javadoc/JavadocClasslikeTemplateMapTest.kt +++ b/plugins/javadoc/src/test/kotlin/org/jetbrains/dokka/javadoc/JavadocClasslikeTemplateMapTest.kt @@ -149,13 +149,13 @@ internal class JavadocClasslikeTemplateMapTest : AbstractJavadocTemplateMapTest( val map = allPagesOfType().first { it.name == "TestClass" }.templateMap assertEquals("TestClass", map["name"]) val signature = assertIsInstance>(map["signature"]) - assertEquals("@Author(name = \"Benjamin Franklin\")", signature["annotations"]) + assertEquals("@Author(name = Benjamin Franklin)", signature["annotations"]) val methods = assertIsInstance>(map["methods"]) val ownMethods = assertIsInstance>(methods["own"]) val method = assertIsInstance>(ownMethods.single()) val methodSignature = assertIsInstance>(method["signature"]) - assertEquals("@Author(name = \"Franklin D. Roosevelt\")", methodSignature["annotations"]) + assertEquals("@Author(name = Franklin D. Roosevelt)", methodSignature["annotations"]) } } diff --git a/plugins/kotlin-as-java/src/main/kotlin/converters/KotlinToJavaConverter.kt b/plugins/kotlin-as-java/src/main/kotlin/converters/KotlinToJavaConverter.kt index 01de516484..e8af51c136 100644 --- a/plugins/kotlin-as-java/src/main/kotlin/converters/KotlinToJavaConverter.kt +++ b/plugins/kotlin-as-java/src/main/kotlin/converters/KotlinToJavaConverter.kt @@ -28,17 +28,17 @@ internal fun DPackage.asJava(): DPackage { .groupBy({ it.first }) { it.second } .map { (syntheticClassName, nodes) -> DClass( - dri = dri.withClass(syntheticClassName), - name = syntheticClassName, + dri = dri.withClass(syntheticClassName.name), + name = syntheticClassName.name, properties = nodes.filterIsInstance().map { it.asJava(true) }, constructors = emptyList(), functions = ( nodes .filterIsInstance() .filterNot { it.isConst } - .flatMap { it.javaAccessors(relocateToClass = syntheticClassName) } + + .flatMap { it.javaAccessors(relocateToClass = syntheticClassName.name) } + nodes.filterIsInstance() - .map { it.asJava(syntheticClassName) }), // TODO: methods are static and receiver is a param + .map { it.asJava(syntheticClassName.name) }), // TODO: methods are static and receiver is a param classlikes = emptyList(), sources = emptyMap(), expectPresentInSet = null, diff --git a/plugins/kotlin-as-java/src/main/kotlin/transformers/JvmNameProvider.kt b/plugins/kotlin-as-java/src/main/kotlin/transformers/JvmNameProvider.kt index 441abb5185..c060fe8846 100644 --- a/plugins/kotlin-as-java/src/main/kotlin/transformers/JvmNameProvider.kt +++ b/plugins/kotlin-as-java/src/main/kotlin/transformers/JvmNameProvider.kt @@ -6,22 +6,30 @@ import org.jetbrains.dokka.model.* import org.jetbrains.dokka.model.properties.WithExtraProperties import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult +data class Name(val fqName: String){ + val name = fqName.substringAfterLast(".") +} + class JvmNameProvider { fun nameFor(entry: T): String where T : Documentable, T : WithExtraProperties = entry.directlyAnnotatedJvmName()?.jvmNameAsString() ?: entry.name ?: throw IllegalStateException("Failed to provide a name for ${entry.javaClass.canonicalName}") - fun nameForSyntheticClass(entry: T): String where T : WithSources, T : WithExtraProperties = - entry.extra[Annotations]?.let { + fun nameForSyntheticClass(entry: T): Name where T : WithSources, T : WithExtraProperties, T: Documentable { + val name = entry.extra[Annotations]?.let { it.fileLevelAnnotations.entries.firstNotNullResult { (_, annotations) -> annotations.jvmNameAnnotation()?.jvmNameAsString() } } ?: entry.sources.entries.first().value.path.split("/").last().split(".").first().capitalize() + "Kt" + return Name("${entry.dri.packageName}.$name") + } - fun nameAsJavaGetter(entry: DProperty): String = entry.getter?.directlyAnnotatedJvmName()?.jvmNameAsString() ?: "get" + entry.name.capitalize() + fun nameAsJavaGetter(entry: DProperty): String = + entry.getter?.directlyAnnotatedJvmName()?.jvmNameAsString() ?: "get" + entry.name.capitalize() - fun nameAsJavaSetter(entry: DProperty): String = entry.setter?.directlyAnnotatedJvmName()?.jvmNameAsString() ?: "set" + entry.name.capitalize() + fun nameAsJavaSetter(entry: DProperty): String = + entry.setter?.directlyAnnotatedJvmName()?.jvmNameAsString() ?: "set" + entry.name.capitalize() private fun List.jvmNameAnnotation(): Annotations.Annotation? = firstOrNull { it.isJvmName() } diff --git a/plugins/kotlin-as-java/src/test/kotlin/KotlinAsJavaPluginTest.kt b/plugins/kotlin-as-java/src/test/kotlin/KotlinAsJavaPluginTest.kt index 5430ea88f1..8e7b798aeb 100644 --- a/plugins/kotlin-as-java/src/test/kotlin/KotlinAsJavaPluginTest.kt +++ b/plugins/kotlin-as-java/src/test/kotlin/KotlinAsJavaPluginTest.kt @@ -330,7 +330,7 @@ class KotlinAsJavaPluginTest : BaseAbstractTest() { ) { renderingStage = { _, _ -> writerPlugin.writer.renderedContent("root/kotlinAsJavaPlugin/-a-b-c/some-fun.html").signature().first().match( - "final ", A("Integer"), A("someFun"), "(", A("Integer"), A("xd"), ")", Span() + "final ", A("Integer"), A("someFun"), "(", A("Integer"), "xd)", Span() ) } } @@ -368,7 +368,7 @@ class KotlinAsJavaPluginTest : BaseAbstractTest() { renderingStage = { _, _ -> writerPlugin.writer.renderedContent("root/kotlinAsJavaPlugin/-a-b-c/some-fun.html").signature().first().match( "final ", A("Integer"), A("someFun"), "(", A("Map"), "<", A("String"), - ", ", A("Integer"), ">", A("xd"), ")", Span() + ", ", A("Integer"), "> xd)", Span() ) } }