Skip to content

Commit

Permalink
KT-50292 - Implement vertical alignment of parameters (#2309)
Browse files Browse the repository at this point in the history
* Implement vertical alignment (wrapping) of parameters for kt

* Add tests for params wrapping and extend matchers to check for classes

* Add distinguishable parameters block to kotlinAsJava, extract common logic

* Create a separate Kind for symbol function parameters
  • Loading branch information
IgnatBeresnev committed Jan 27, 2022
1 parent 7c44db1 commit e0a10c2
Show file tree
Hide file tree
Showing 17 changed files with 439 additions and 116 deletions.
9 changes: 9 additions & 0 deletions core/api/core.api
Expand Up @@ -3862,10 +3862,12 @@ public final class org/jetbrains/dokka/pages/ContentResolvedLink : org/jetbrains
public final class org/jetbrains/dokka/pages/ContentStyle : java/lang/Enum, org/jetbrains/dokka/pages/Style {
public static final field Caption Lorg/jetbrains/dokka/pages/ContentStyle;
public static final field InDocumentationAnchor Lorg/jetbrains/dokka/pages/ContentStyle;
public static final field Indented Lorg/jetbrains/dokka/pages/ContentStyle;
public static final field RowTitle Lorg/jetbrains/dokka/pages/ContentStyle;
public static final field RunnableSample Lorg/jetbrains/dokka/pages/ContentStyle;
public static final field TabbedContent Lorg/jetbrains/dokka/pages/ContentStyle;
public static final field WithExtraAttributes Lorg/jetbrains/dokka/pages/ContentStyle;
public static final field Wrapped Lorg/jetbrains/dokka/pages/ContentStyle;
public static fun valueOf (Ljava/lang/String;)Lorg/jetbrains/dokka/pages/ContentStyle;
public static fun values ()[Lorg/jetbrains/dokka/pages/ContentStyle;
}
Expand Down Expand Up @@ -4197,6 +4199,13 @@ public final class org/jetbrains/dokka/pages/SimpleAttr$SimpleAttrKey : org/jetb
public abstract interface class org/jetbrains/dokka/pages/Style {
}

public final class org/jetbrains/dokka/pages/SymbolContentKind : java/lang/Enum, org/jetbrains/dokka/pages/Kind {
public static final field Parameter Lorg/jetbrains/dokka/pages/SymbolContentKind;
public static final field Parameters Lorg/jetbrains/dokka/pages/SymbolContentKind;
public static fun valueOf (Ljava/lang/String;)Lorg/jetbrains/dokka/pages/SymbolContentKind;
public static fun values ()[Lorg/jetbrains/dokka/pages/SymbolContentKind;
}

public final class org/jetbrains/dokka/pages/TextStyle : java/lang/Enum, org/jetbrains/dokka/pages/Style {
public static final field Block Lorg/jetbrains/dokka/pages/TextStyle;
public static final field Bold Lorg/jetbrains/dokka/pages/TextStyle;
Expand Down
45 changes: 43 additions & 2 deletions core/src/main/kotlin/pages/ContentNodes.kt
Expand Up @@ -316,9 +316,25 @@ data class PlatformHintedContent(
interface Style
interface Kind

/**
* [ContentKind] represents a grouping of content of one kind. This can be rendered
* as either a part of a composite page (one tab/block within a class's page, for instance)
* or as a separate page altogether.
*/
enum class ContentKind : Kind {

Comment, Constructors, Functions, Parameters, Properties, Classlikes, Packages, Symbol, Sample, Main, BriefComment,
/**
* Marks all sorts of signatures. Can contain sub-kinds marked as [SymbolContentKind]
*
* Some examples:
* - primary constructor: `data class CoroutineName(name: String) : AbstractCoroutineContextElement`
* - constructor: `fun CoroutineName(name: String)`
* - function: `open override fun toString(): String`
* - property: `val name: String`
*/
Symbol,

Comment, Constructors, Functions, Parameters, Properties, Classlikes, Packages, Sample, Main, BriefComment,
Empty, Source, TypeAliases, Cover, Inheritors, SourceSetDependentHint, Extensions, Annotations;

companion object {
Expand All @@ -338,6 +354,30 @@ enum class ContentKind : Kind {
fun shouldBePlatformTagged(kind: Kind): Boolean = kind in platformTagged
}
}

/**
* Content kind for [ContentKind.Symbol] content, which is essentially about signatures
*/
enum class SymbolContentKind : Kind {
/**
* Marks constructor/function parameters, everything in-between parentheses.
*
* For function `fun foo(bar: String, baz: Int, qux: Boolean)`,
* the parameters would be the whole of `bar: String, baz: Int, qux: Boolean`
*/
Parameters,

/**
* Marks a single parameter in a function. Most likely to be a child of [Parameters].
*
* In function `fun foo(bar: String, baz: Int, qux: Boolean)` there would be 3 [Parameter] instances:
* - `bar: String, `
* - `baz: Int, `
* - `qux: Boolean`
*/
Parameter,
}

enum class TokenStyle : Style {
Keyword, Punctuation, Function, Operator, Annotation, Number, String, Boolean, Constant, Builtin
}
Expand All @@ -347,7 +387,8 @@ enum class TextStyle : Style {
}

enum class ContentStyle : Style {
RowTitle, TabbedContent, WithExtraAttributes, RunnableSample, InDocumentationAnchor, Caption
RowTitle, TabbedContent, WithExtraAttributes, RunnableSample, InDocumentationAnchor, Caption,
Wrapped, Indented
}

enum class ListStyle : Style {
Expand Down
4 changes: 4 additions & 0 deletions plugins/base/api/base.api
Expand Up @@ -824,6 +824,7 @@ public abstract interface class org/jetbrains/dokka/base/signatures/JvmSignature
public abstract fun annotationsInline (Lorg/jetbrains/dokka/base/translators/documentables/PageContentBuilder$DocumentableContentBuilder;Lorg/jetbrains/dokka/model/AnnotationTarget;)V
public abstract fun annotationsInlineWithIgnored (Lorg/jetbrains/dokka/base/translators/documentables/PageContentBuilder$DocumentableContentBuilder;Lorg/jetbrains/dokka/model/AnnotationTarget;Ljava/util/Set;Lorg/jetbrains/dokka/base/signatures/AtStrategy;Lkotlin/Pair;Ljava/lang/String;)V
public abstract fun modifiers (Lorg/jetbrains/dokka/model/properties/WithExtraProperties;)Ljava/util/Map;
public abstract fun parametersBlock (Lorg/jetbrains/dokka/base/translators/documentables/PageContentBuilder$DocumentableContentBuilder;Lorg/jetbrains/dokka/model/DFunction;Lkotlin/jvm/functions/Function2;)V
public abstract fun plus (Ljava/util/Map;Ljava/util/Map;)Ljava/util/Map;
public abstract fun stylesIfDeprecated (Lorg/jetbrains/dokka/model/properties/WithExtraProperties;Lorg/jetbrains/dokka/DokkaConfiguration$DokkaSourceSet;)Ljava/util/Set;
public abstract fun toSignatureString (Ljava/util/Collection;)Ljava/lang/String;
Expand All @@ -836,6 +837,7 @@ public final class org/jetbrains/dokka/base/signatures/JvmSignatureUtils$Default
public static fun annotations (Lorg/jetbrains/dokka/base/signatures/JvmSignatureUtils;Lorg/jetbrains/dokka/model/properties/WithExtraProperties;)Ljava/util/Map;
public static fun annotationsBlockWithIgnored (Lorg/jetbrains/dokka/base/signatures/JvmSignatureUtils;Lorg/jetbrains/dokka/base/translators/documentables/PageContentBuilder$DocumentableContentBuilder;Lorg/jetbrains/dokka/model/AnnotationTarget;Ljava/util/Set;Lorg/jetbrains/dokka/base/signatures/AtStrategy;Lkotlin/Pair;Ljava/lang/String;)V
public static fun annotationsInlineWithIgnored (Lorg/jetbrains/dokka/base/signatures/JvmSignatureUtils;Lorg/jetbrains/dokka/base/translators/documentables/PageContentBuilder$DocumentableContentBuilder;Lorg/jetbrains/dokka/model/AnnotationTarget;Ljava/util/Set;Lorg/jetbrains/dokka/base/signatures/AtStrategy;Lkotlin/Pair;Ljava/lang/String;)V
public static fun parametersBlock (Lorg/jetbrains/dokka/base/signatures/JvmSignatureUtils;Lorg/jetbrains/dokka/base/translators/documentables/PageContentBuilder$DocumentableContentBuilder;Lorg/jetbrains/dokka/model/DFunction;Lkotlin/jvm/functions/Function2;)V
public static fun plus (Lorg/jetbrains/dokka/base/signatures/JvmSignatureUtils;Ljava/util/Map;Ljava/util/Map;)Ljava/util/Map;
public static fun stylesIfDeprecated (Lorg/jetbrains/dokka/base/signatures/JvmSignatureUtils;Lorg/jetbrains/dokka/model/properties/WithExtraProperties;Lorg/jetbrains/dokka/DokkaConfiguration$DokkaSourceSet;)Ljava/util/Set;
public static fun toSignatureString (Lorg/jetbrains/dokka/base/signatures/JvmSignatureUtils;Ljava/util/Collection;)Ljava/lang/String;
Expand All @@ -853,6 +855,7 @@ public final class org/jetbrains/dokka/base/signatures/KotlinSignatureProvider :
public fun annotationsInline (Lorg/jetbrains/dokka/base/translators/documentables/PageContentBuilder$DocumentableContentBuilder;Lorg/jetbrains/dokka/model/AnnotationTarget;)V
public fun annotationsInlineWithIgnored (Lorg/jetbrains/dokka/base/translators/documentables/PageContentBuilder$DocumentableContentBuilder;Lorg/jetbrains/dokka/model/AnnotationTarget;Ljava/util/Set;Lorg/jetbrains/dokka/base/signatures/AtStrategy;Lkotlin/Pair;Ljava/lang/String;)V
public fun modifiers (Lorg/jetbrains/dokka/model/properties/WithExtraProperties;)Ljava/util/Map;
public fun parametersBlock (Lorg/jetbrains/dokka/base/translators/documentables/PageContentBuilder$DocumentableContentBuilder;Lorg/jetbrains/dokka/model/DFunction;Lkotlin/jvm/functions/Function2;)V
public fun plus (Ljava/util/Map;Ljava/util/Map;)Ljava/util/Map;
public fun signature (Lorg/jetbrains/dokka/model/Documentable;)Ljava/util/List;
public fun stylesIfDeprecated (Lorg/jetbrains/dokka/model/properties/WithExtraProperties;Lorg/jetbrains/dokka/DokkaConfiguration$DokkaSourceSet;)Ljava/util/Set;
Expand All @@ -873,6 +876,7 @@ public final class org/jetbrains/dokka/base/signatures/KotlinSignatureUtils : or
public final fun getDriOrNull (Lorg/jetbrains/dokka/model/Bound;)Lorg/jetbrains/dokka/links/DRI;
public final fun getDrisOfAllNestedBounds (Lorg/jetbrains/dokka/model/Projection;)Ljava/util/List;
public fun modifiers (Lorg/jetbrains/dokka/model/properties/WithExtraProperties;)Ljava/util/Map;
public fun parametersBlock (Lorg/jetbrains/dokka/base/translators/documentables/PageContentBuilder$DocumentableContentBuilder;Lorg/jetbrains/dokka/model/DFunction;Lkotlin/jvm/functions/Function2;)V
public fun plus (Ljava/util/Map;Ljava/util/Map;)Ljava/util/Map;
public fun stylesIfDeprecated (Lorg/jetbrains/dokka/model/properties/WithExtraProperties;Lorg/jetbrains/dokka/DokkaConfiguration$DokkaSourceSet;)Ljava/util/Set;
public fun toSignatureString (Ljava/util/Collection;)Ljava/lang/String;
Expand Down
13 changes: 12 additions & 1 deletion plugins/base/base-test-utils/api/base-test-utils.api
Expand Up @@ -85,6 +85,14 @@ public final class renderers/TestPageKt {
public static final fun testPage (Lkotlin/jvm/functions/Function1;)Lrenderers/RawTestPage;
}

public final class signatures/Parameter : utils/Tag {
public fun <init> ([Ljava/lang/Object;)V
}

public final class signatures/Parameters : utils/Tag {
public fun <init> ([Ljava/lang/Object;)V
}

public final class signatures/SignatureUtilsKt {
public static final fun firstSignature (Lorg/jsoup/nodes/Element;)Lorg/jsoup/nodes/Element;
public static final fun renderedContent (Lutils/TestOutputWriter;Ljava/lang/String;)Lorg/jsoup/nodes/Element;
Expand Down Expand Up @@ -131,6 +139,7 @@ public final class utils/I : utils/Tag {
public final class utils/JsoupUtilsKt {
public static final fun match (Lorg/jsoup/nodes/Element;[Ljava/lang/Object;Z)V
public static synthetic fun match$default (Lorg/jsoup/nodes/Element;[Ljava/lang/Object;ZILjava/lang/Object;)V
public static final fun withClasses (Lutils/Tag;[Ljava/lang/String;)Lutils/Tag;
}

public final class utils/P : utils/Tag {
Expand All @@ -146,7 +155,9 @@ public final class utils/Span : utils/Tag {
}

public class utils/Tag {
public fun <init> (Ljava/lang/String;[Ljava/lang/Object;)V
public fun <init> (Ljava/lang/String;[Ljava/lang/Object;Ljava/util/List;)V
public synthetic fun <init> (Ljava/lang/String;[Ljava/lang/Object;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getExpectedClasses ()Ljava/util/List;
public final fun getMatchers ()[Ljava/lang/Object;
public final fun getName ()Ljava/lang/String;
}
Expand Down
Expand Up @@ -21,7 +21,7 @@ fun Element.match(vararg matchers: Any, ignoreSpanWithTokenStyle:Boolean = false
.zip(matchers)
.forEach { (n, m) -> m.accepts(n, ignoreSpan = ignoreSpanWithTokenStyle) }

open class Tag(val name: String, vararg val matchers: Any)
open class Tag(val name: String, vararg val matchers: Any, val expectedClasses: List<String> = emptyList())
class Div(vararg matchers: Any) : Tag("div", *matchers)
class P(vararg matchers: Any) : Tag("p", *matchers)
class Span(vararg matchers: Any) : Tag("span", *matchers)
Expand All @@ -34,16 +34,24 @@ class Dt(vararg matchers: Any) : Tag("dt", *matchers)
class Dd(vararg matchers: Any) : Tag("dd", *matchers)
object Wbr : Tag("wbr")
object Br : Tag("br")

fun Tag.withClasses(vararg classes: String) = Tag(name, *matchers, expectedClasses = classes.toList())

private fun Any.accepts(n: Node, ignoreSpan:Boolean = true) {
when (this) {
is String -> assert(n is TextNode && n.text().trim() == this.trim()) { "\"$this\" expected but found: $n" }
is Tag -> {
assert(n is Element && n.tagName() == name) { "Tag $name expected but found: $n" }
if (n is Element && matchers.isNotEmpty()) n.match(*matchers, ignoreSpanWithTokenStyle = ignoreSpan)
check(n is Element) { "Expected node to be Element: $n" }
assert(n.tagName() == name) { "Tag \"$name\" expected but found: \"$n\"" }
expectedClasses.forEach {
assert(n.hasClass(it)) { "Expected to find class \"$it\" for tag \"$name\", found: ${n.classNames()}" }
}
if (matchers.isNotEmpty()) n.match(*matchers, ignoreSpanWithTokenStyle = ignoreSpan)
}
else -> throw IllegalArgumentException("$this is not proper matcher")
}
}

private fun List<Node>.uniteConsecutiveTextNodes(): MutableList<Node> {
val resList = mutableListOf<Node>()
var acc = StringBuilder()
Expand Down
Expand Up @@ -2,11 +2,15 @@ package signatures

import org.jsoup.Jsoup
import org.jsoup.nodes.Element
import utils.Tag
import utils.TestOutputWriter

fun TestOutputWriter.renderedContent(path: String = "root/example.html") =
contents.getValue(path).let { Jsoup.parse(it) }.select("#content")
.single()

fun Element.signature() = select("div.symbol.monospace")
fun Element.firstSignature() = signature().first()
fun Element.firstSignature() = signature().first()

class Parameters(vararg matchers: Any) : Tag("span", *matchers, expectedClasses = listOf("parameters"))
class Parameter(vararg matchers: Any) : Tag("span", *matchers, expectedClasses = listOf("parameter"))
15 changes: 15 additions & 0 deletions plugins/base/src/main/kotlin/renderers/html/HtmlRenderer.kt
Expand Up @@ -98,6 +98,21 @@ open class HtmlRenderer(
}
node.hasStyle(TextStyle.Span) -> span { childrenCallback() }
node.dci.kind == ContentKind.Symbol -> div("symbol $additionalClasses") { childrenCallback() }
node.dci.kind == SymbolContentKind.Parameters -> {
span("parameters $additionalClasses") {
childrenCallback()
}
}
node.dci.kind == SymbolContentKind.Parameter -> {
span("parameter $additionalClasses") {
if (node.hasStyle(ContentStyle.Indented)) {
// could've been done with CSS (padding-left, ::before, etc), but the indent needs to
// consist of physical spaces, otherwise select and copy won't work properly
repeat(4) { consumer.onTagContentEntity(Entities.nbsp) }
}
childrenCallback()
}
}
node.dci.kind == ContentKind.BriefComment -> div("brief $additionalClasses") { childrenCallback() }
node.dci.kind == ContentKind.Cover -> div("cover $additionalClasses") { //TODO this can be removed
childrenCallback()
Expand Down
51 changes: 51 additions & 0 deletions plugins/base/src/main/kotlin/signatures/JvmSignatureUtils.kt
Expand Up @@ -174,6 +174,57 @@ interface JvmSignatureUtils {
parameters.flatMap { listOf(it.dri) + it.type.drisOfAllNestedBounds })
return t.dri in allDris
}

/**
* Builds a distinguishable [function] parameters block, so that it
* can be processed or custom rendered down the road.
*
* Resulting structure:
* ```
* SymbolContentKind.Parameters(style = wrapped) {
* SymbolContentKind.Parameter(style = indented) { param, }
* SymbolContentKind.Parameter(style = indented) { param, }
* SymbolContentKind.Parameter(style = indented) { param }
* }
* ```
* Wrapping and indentation of parameters is applied conditionally, see [shouldWrapParams]
*/
fun PageContentBuilder.DocumentableContentBuilder.parametersBlock(
function: DFunction, paramBuilder: PageContentBuilder.DocumentableContentBuilder.(DParameter) -> Unit
) {
val shouldWrap = function.shouldWrapParams()
val parametersStyle = if (shouldWrap) setOf(ContentStyle.Wrapped) else emptySet()
val elementStyle = if (shouldWrap) setOf(ContentStyle.Indented) else emptySet()
group(kind = SymbolContentKind.Parameters, styles = parametersStyle) {
function.parameters.dropLast(1).forEach {
group(kind = SymbolContentKind.Parameter, styles = elementStyle) {
paramBuilder(it)
punctuation(", ")
}
}
group(kind = SymbolContentKind.Parameter, styles = elementStyle) {
paramBuilder(function.parameters.last())
}
}
}

/**
* Determines whether parameters in a function (including constructor) should be wrapped
*
* Without wrapping:
* ```
* class SimpleClass(foo: String, bar: String) {}
* ```
* With wrapping:
* ```
* class SimpleClass(
* foo: String,
* bar: String,
* baz: String
* )
* ```
*/
private fun DFunction.shouldWrapParams() = this.parameters.size >= 3
}

sealed class AtStrategy
Expand Down

0 comments on commit e0a10c2

Please sign in to comment.