aboutsummaryrefslogtreecommitdiff
path: root/dokka-subprojects/core-content-matcher-test-utils
diff options
context:
space:
mode:
authorIgnat Beresnev <ignat.beresnev@jetbrains.com>2023-11-10 11:46:54 +0100
committerGitHub <noreply@github.com>2023-11-10 11:46:54 +0100
commit8e5c63d035ef44a269b8c43430f43f5c8eebfb63 (patch)
tree1b915207b2b9f61951ddbf0ff2e687efd053d555 /dokka-subprojects/core-content-matcher-test-utils
parenta44efd4ba0c2e4ab921ff75e0f53fc9335aa79db (diff)
downloaddokka-8e5c63d035ef44a269b8c43430f43f5c8eebfb63.tar.gz
dokka-8e5c63d035ef44a269b8c43430f43f5c8eebfb63.tar.bz2
dokka-8e5c63d035ef44a269b8c43430f43f5c8eebfb63.zip
Restructure the project to utilize included builds (#3174)
* Refactor and simplify artifact publishing * Update Gradle to 8.4 * Refactor and simplify convention plugins and build scripts Fixes #3132 --------- Co-authored-by: Adam <897017+aSemy@users.noreply.github.com> Co-authored-by: Oleg Yukhnevich <whyoleg@gmail.com>
Diffstat (limited to 'dokka-subprojects/core-content-matcher-test-utils')
-rw-r--r--dokka-subprojects/core-content-matcher-test-utils/api/core-content-matcher-test-utils.api81
-rw-r--r--dokka-subprojects/core-content-matcher-test-utils/build.gradle.kts14
-rw-r--r--dokka-subprojects/core-content-matcher-test-utils/src/main/kotlin/org/jetbrains/dokka/test/tools/matchers/content/ContentMatchersDsl.kt191
-rw-r--r--dokka-subprojects/core-content-matcher-test-utils/src/main/kotlin/org/jetbrains/dokka/test/tools/matchers/content/contentMatchers.kt191
4 files changed, 477 insertions, 0 deletions
diff --git a/dokka-subprojects/core-content-matcher-test-utils/api/core-content-matcher-test-utils.api b/dokka-subprojects/core-content-matcher-test-utils/api/core-content-matcher-test-utils.api
new file mode 100644
index 00000000..58881a15
--- /dev/null
+++ b/dokka-subprojects/core-content-matcher-test-utils/api/core-content-matcher-test-utils.api
@@ -0,0 +1,81 @@
+public final class matchers/content/ContentMatcherBuilder {
+ public fun <init> (Lkotlin/reflect/KClass;)V
+ public final fun build ()Lorg/jetbrains/dokka/test/tools/matchers/content/CompositeMatcher;
+ public final fun getChildren ()Ljava/util/List;
+ public final fun unaryPlus (Ljava/lang/String;)V
+}
+
+public abstract interface annotation class matchers/content/ContentMatchersDsl : java/lang/annotation/Annotation {
+}
+
+public final class matchers/content/ContentMatchersDslKt {
+ public static final fun after (Lmatchers/content/ContentMatcherBuilder;Lkotlin/jvm/functions/Function1;)V
+ public static final fun assertNode (Lorg/jetbrains/dokka/pages/ContentNode;Lkotlin/jvm/functions/Function1;)V
+ public static final fun before (Lmatchers/content/ContentMatcherBuilder;Lkotlin/jvm/functions/Function1;)V
+ public static final fun br (Lmatchers/content/ContentMatcherBuilder;)V
+ public static final fun caption (Lmatchers/content/ContentMatcherBuilder;Lkotlin/jvm/functions/Function1;)V
+ public static final fun check (Lmatchers/content/ContentMatcherBuilder;Lkotlin/jvm/functions/Function1;)V
+ public static final fun codeBlock (Lmatchers/content/ContentMatcherBuilder;Lkotlin/jvm/functions/Function1;)V
+ public static final fun codeInline (Lmatchers/content/ContentMatcherBuilder;Lkotlin/jvm/functions/Function1;)V
+ public static final fun divergent (Lmatchers/content/ContentMatcherBuilder;Lkotlin/jvm/functions/Function1;)V
+ public static final fun divergentGroup (Lmatchers/content/ContentMatcherBuilder;Lkotlin/jvm/functions/Function1;)V
+ public static final fun divergentInstance (Lmatchers/content/ContentMatcherBuilder;Lkotlin/jvm/functions/Function1;)V
+ public static final fun group (Lmatchers/content/ContentMatcherBuilder;Lkotlin/jvm/functions/Function1;)V
+ public static final fun hasExactText (Lmatchers/content/ContentMatcherBuilder;Ljava/lang/String;)V
+ public static final fun header (Lmatchers/content/ContentMatcherBuilder;Ljava/lang/Integer;Lkotlin/jvm/functions/Function1;)V
+ public static synthetic fun header$default (Lmatchers/content/ContentMatcherBuilder;Ljava/lang/Integer;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
+ public static final fun link (Lmatchers/content/ContentMatcherBuilder;Lkotlin/jvm/functions/Function1;)V
+ public static final fun list (Lmatchers/content/ContentMatcherBuilder;Lkotlin/jvm/functions/Function1;)V
+ public static final fun p (Lmatchers/content/ContentMatcherBuilder;Lkotlin/jvm/functions/Function1;)V
+ public static final fun platformHinted (Lmatchers/content/ContentMatcherBuilder;Lkotlin/jvm/functions/Function1;)V
+ public static final fun skipAllNotMatching (Lmatchers/content/ContentMatcherBuilder;)V
+ public static final fun somewhere (Lmatchers/content/ContentMatcherBuilder;Lkotlin/jvm/functions/Function1;)V
+ public static final fun tab (Lmatchers/content/ContentMatcherBuilder;Lorg/jetbrains/dokka/pages/TabbedContentType;Lkotlin/jvm/functions/Function1;)V
+ public static final fun tabbedGroup (Lmatchers/content/ContentMatcherBuilder;Lkotlin/jvm/functions/Function1;)V
+ public static final fun table (Lmatchers/content/ContentMatcherBuilder;Lkotlin/jvm/functions/Function1;)V
+}
+
+public final class org/jetbrains/dokka/test/tools/matchers/content/Anything : org/jetbrains/dokka/test/tools/matchers/content/MatcherElement {
+ public static final field INSTANCE Lorg/jetbrains/dokka/test/tools/matchers/content/Anything;
+}
+
+public final class org/jetbrains/dokka/test/tools/matchers/content/CompositeMatcher : org/jetbrains/dokka/test/tools/matchers/content/NodeMatcher {
+ public fun <init> (Lkotlin/reflect/KClass;Ljava/util/List;Lkotlin/jvm/functions/Function1;)V
+ public synthetic fun <init> (Lkotlin/reflect/KClass;Ljava/util/List;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public fun tryMatch (Lorg/jetbrains/dokka/pages/ContentNode;)V
+}
+
+public abstract class org/jetbrains/dokka/test/tools/matchers/content/MatcherElement {
+}
+
+public final class org/jetbrains/dokka/test/tools/matchers/content/MatcherError : java/lang/AssertionError {
+ public fun <init> (Ljava/lang/String;Lorg/jetbrains/dokka/test/tools/matchers/content/MatcherElement;ZLjava/lang/Throwable;)V
+ public synthetic fun <init> (Ljava/lang/String;Lorg/jetbrains/dokka/test/tools/matchers/content/MatcherElement;ZLjava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Ljava/lang/String;
+ public final fun component2 ()Lorg/jetbrains/dokka/test/tools/matchers/content/MatcherElement;
+ public final fun component3 ()Z
+ public final fun component4 ()Ljava/lang/Throwable;
+ public final fun copy (Ljava/lang/String;Lorg/jetbrains/dokka/test/tools/matchers/content/MatcherElement;ZLjava/lang/Throwable;)Lorg/jetbrains/dokka/test/tools/matchers/content/MatcherError;
+ public static synthetic fun copy$default (Lorg/jetbrains/dokka/test/tools/matchers/content/MatcherError;Ljava/lang/String;Lorg/jetbrains/dokka/test/tools/matchers/content/MatcherElement;ZLjava/lang/Throwable;ILjava/lang/Object;)Lorg/jetbrains/dokka/test/tools/matchers/content/MatcherError;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getAnchor ()Lorg/jetbrains/dokka/test/tools/matchers/content/MatcherElement;
+ public final fun getAnchorAfter ()Z
+ public fun getCause ()Ljava/lang/Throwable;
+ public fun getMessage ()Ljava/lang/String;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public class org/jetbrains/dokka/test/tools/matchers/content/NodeMatcher : org/jetbrains/dokka/test/tools/matchers/content/MatcherElement {
+ public fun <init> (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V
+ public synthetic fun <init> (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun getAssertions ()Lkotlin/jvm/functions/Function1;
+ public final fun getKclass ()Lkotlin/reflect/KClass;
+ public fun tryMatch (Lorg/jetbrains/dokka/pages/ContentNode;)V
+}
+
+public final class org/jetbrains/dokka/test/tools/matchers/content/TextMatcher : org/jetbrains/dokka/test/tools/matchers/content/MatcherElement {
+ public fun <init> (Ljava/lang/String;)V
+ public final fun getText ()Ljava/lang/String;
+}
+
diff --git a/dokka-subprojects/core-content-matcher-test-utils/build.gradle.kts b/dokka-subprojects/core-content-matcher-test-utils/build.gradle.kts
new file mode 100644
index 00000000..4207c31b
--- /dev/null
+++ b/dokka-subprojects/core-content-matcher-test-utils/build.gradle.kts
@@ -0,0 +1,14 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+plugins {
+ id("dokkabuild.kotlin-jvm")
+}
+
+dependencies {
+ implementation(projects.dokkaSubprojects.coreTestApi)
+
+ implementation(kotlin("reflect"))
+ implementation(kotlin("test"))
+}
diff --git a/dokka-subprojects/core-content-matcher-test-utils/src/main/kotlin/org/jetbrains/dokka/test/tools/matchers/content/ContentMatchersDsl.kt b/dokka-subprojects/core-content-matcher-test-utils/src/main/kotlin/org/jetbrains/dokka/test/tools/matchers/content/ContentMatchersDsl.kt
new file mode 100644
index 00000000..026f7b6b
--- /dev/null
+++ b/dokka-subprojects/core-content-matcher-test-utils/src/main/kotlin/org/jetbrains/dokka/test/tools/matchers/content/ContentMatchersDsl.kt
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package matchers.content
+
+import org.jetbrains.dokka.model.withDescendants
+import org.jetbrains.dokka.pages.*
+import org.jetbrains.dokka.test.tools.matchers.content.*
+import kotlin.reflect.KClass
+import kotlin.test.assertEquals
+import kotlin.test.asserter
+
+// entry point:
+public fun ContentNode.assertNode(block: ContentMatcherBuilder<ContentComposite>.() -> Unit) {
+ val matcher = ContentMatcherBuilder(ContentComposite::class).apply(block).build()
+ try {
+ matcher.tryMatch(this)
+ } catch (e: MatcherError) {
+ throw AssertionError(e.message + "\n" + matcher.toDebugString(e.anchor, e.anchorAfter))
+ }
+}
+
+
+// DSL:
+@DslMarker
+public annotation class ContentMatchersDsl
+
+@ContentMatchersDsl
+public class ContentMatcherBuilder<T : ContentComposite> @PublishedApi internal constructor(private val kclass: KClass<T>) {
+ @PublishedApi
+ internal val children: MutableList<MatcherElement> = mutableListOf()
+ internal val assertions = mutableListOf<T.() -> Unit>()
+
+ public fun build(): CompositeMatcher<T> = CompositeMatcher(kclass, childrenOrSkip()) { assertions.forEach { it() } }
+
+ // part of DSL that cannot be defined as an extension
+ public operator fun String.unaryPlus() {
+ children += TextMatcher(this)
+ }
+
+ private fun childrenOrSkip() = if (children.isEmpty() && assertions.isNotEmpty()) listOf(Anything) else children
+}
+
+public fun <T : ContentComposite> ContentMatcherBuilder<T>.check(assertion: T.() -> Unit) {
+ assertions += assertion
+}
+
+private val ContentComposite.extractedText
+ get() = withDescendants().filterIsInstance<ContentText>().joinToString(separator = "") { it.text }
+
+public fun <T : ContentComposite> ContentMatcherBuilder<T>.hasExactText(expected: String) {
+ assertions += {
+ assertEquals(expected, this.extractedText)
+ }
+}
+
+public inline fun <reified S : ContentComposite> ContentMatcherBuilder<*>.composite(
+ block: ContentMatcherBuilder<S>.() -> Unit
+) {
+ children += ContentMatcherBuilder(S::class).apply(block).build()
+}
+
+public inline fun <reified S : ContentNode> ContentMatcherBuilder<*>.node(noinline assertions: S.() -> Unit = {}) {
+ children += NodeMatcher(S::class, assertions)
+}
+
+public fun ContentMatcherBuilder<*>.skipAllNotMatching() {
+ children += Anything
+}
+
+
+// Convenience functions:
+public fun ContentMatcherBuilder<*>.group(block: ContentMatcherBuilder<ContentGroup>.() -> Unit) {
+ composite(block)
+}
+
+public fun ContentMatcherBuilder<*>.tabbedGroup(
+ block: ContentMatcherBuilder<ContentGroup>.() -> Unit
+) {
+ composite<ContentGroup> {
+ block()
+ check { assertContains(this.style, ContentStyle.TabbedContent) }
+ }
+}
+
+public fun ContentMatcherBuilder<*>.tab(
+ tabbedContentType: TabbedContentType, block: ContentMatcherBuilder<ContentGroup>.() -> Unit
+) {
+ composite<ContentGroup> {
+ block()
+ check {
+ assertEquals(tabbedContentType, this.extra[TabbedContentTypeExtra]?.value)
+ }
+ }
+}
+
+public fun ContentMatcherBuilder<*>.header(expectedLevel: Int? = null, block: ContentMatcherBuilder<ContentHeader>.() -> Unit) {
+ composite<ContentHeader> {
+ block()
+ check { if (expectedLevel != null) assertEquals(expectedLevel, this.level) }
+ }
+}
+
+public fun ContentMatcherBuilder<*>.p(block: ContentMatcherBuilder<ContentGroup>.() -> Unit) {
+ composite<ContentGroup> {
+ block()
+ check { assertContains(this.style, TextStyle.Paragraph) }
+ }
+}
+
+public fun ContentMatcherBuilder<*>.link(block: ContentMatcherBuilder<ContentLink>.() -> Unit) {
+ composite(block)
+}
+
+public fun ContentMatcherBuilder<*>.table(block: ContentMatcherBuilder<ContentTable>.() -> Unit) {
+ composite(block)
+}
+
+public fun ContentMatcherBuilder<*>.platformHinted(block: ContentMatcherBuilder<ContentGroup>.() -> Unit) {
+ composite<PlatformHintedContent> { group(block) }
+}
+
+public fun ContentMatcherBuilder<*>.list(block: ContentMatcherBuilder<ContentList>.() -> Unit) {
+ composite(block)
+}
+
+public fun ContentMatcherBuilder<*>.codeBlock(block: ContentMatcherBuilder<ContentCodeBlock>.() -> Unit) {
+ composite(block)
+}
+
+public fun ContentMatcherBuilder<*>.codeInline(block: ContentMatcherBuilder<ContentCodeInline>.() -> Unit) {
+ composite(block)
+}
+
+public fun ContentMatcherBuilder<*>.caption(block: ContentMatcherBuilder<ContentGroup>.() -> Unit) {
+ composite<ContentGroup> {
+ block()
+ check { assertContains(this.style, ContentStyle.Caption) }
+ }
+}
+
+public fun ContentMatcherBuilder<*>.br() {
+ node<ContentBreakLine>()
+}
+
+public fun ContentMatcherBuilder<*>.somewhere(block: ContentMatcherBuilder<*>.() -> Unit) {
+ skipAllNotMatching()
+ block()
+ skipAllNotMatching()
+}
+
+public fun ContentMatcherBuilder<*>.divergentGroup(
+ block: ContentMatcherBuilder<ContentDivergentGroup>.() -> Unit
+) {
+ composite(block)
+}
+
+public fun ContentMatcherBuilder<ContentDivergentGroup>.divergentInstance(
+ block: ContentMatcherBuilder<ContentDivergentInstance>.() -> Unit
+) {
+ composite(block)
+}
+
+public fun ContentMatcherBuilder<ContentDivergentInstance>.before(
+ block: ContentMatcherBuilder<ContentComposite>.() -> Unit
+) {
+ composite(block)
+}
+
+public fun ContentMatcherBuilder<ContentDivergentInstance>.divergent(
+ block: ContentMatcherBuilder<ContentComposite>.() -> Unit
+) {
+ composite(block)
+}
+
+public fun ContentMatcherBuilder<ContentDivergentInstance>.after(
+ block: ContentMatcherBuilder<ContentComposite>.() -> Unit
+) {
+ composite(block)
+}
+
+/*
+ * TODO replace with kotlin.test.assertContains after migrating to Kotlin language version 1.5+
+ */
+private fun <T> assertContains(iterable: Iterable<T>, element: T) {
+ asserter.assertTrue(
+ { "Expected the collection to contain the element.\nCollection <$iterable>, element <$element>." },
+ iterable.contains(element)
+ )
+}
diff --git a/dokka-subprojects/core-content-matcher-test-utils/src/main/kotlin/org/jetbrains/dokka/test/tools/matchers/content/contentMatchers.kt b/dokka-subprojects/core-content-matcher-test-utils/src/main/kotlin/org/jetbrains/dokka/test/tools/matchers/content/contentMatchers.kt
new file mode 100644
index 00000000..412f728b
--- /dev/null
+++ b/dokka-subprojects/core-content-matcher-test-utils/src/main/kotlin/org/jetbrains/dokka/test/tools/matchers/content/contentMatchers.kt
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("PackageDirectoryMismatch")
+
+package org.jetbrains.dokka.test.tools.matchers.content
+
+import org.jetbrains.dokka.model.asPrintableTree
+import org.jetbrains.dokka.pages.ContentComposite
+import org.jetbrains.dokka.pages.ContentNode
+import org.jetbrains.dokka.pages.ContentText
+import kotlin.reflect.KClass
+import kotlin.reflect.full.cast
+import kotlin.reflect.full.safeCast
+
+public sealed class MatcherElement
+
+public class TextMatcher(
+ public val text: String
+) : MatcherElement()
+
+public open class NodeMatcher<T : ContentNode>(
+ public val kclass: KClass<T>,
+ public val assertions: T.() -> Unit = {}
+) : MatcherElement() {
+
+ public open fun tryMatch(node: ContentNode) {
+ kclass.safeCast(node)?.apply {
+ try {
+ assertions()
+ } catch (e: AssertionError) {
+ throw MatcherError(
+ "${e.message.orEmpty()}\nin node:\n${node.debugRepresentation()}",
+ this@NodeMatcher,
+ cause = e
+ )
+ }
+ } ?: throw MatcherError("Expected ${kclass.simpleName} but got:\n${node.debugRepresentation()}", this)
+ }
+}
+
+public class CompositeMatcher<T : ContentComposite>(
+ kclass: KClass<T>,
+ private val children: List<MatcherElement>,
+ assertions: T.() -> Unit = {}
+) : NodeMatcher<T>(kclass, assertions) {
+
+ internal val normalizedChildren: List<MatcherElement> by lazy {
+ children.fold(listOf()) { acc, e ->
+ when {
+ acc.lastOrNull() is Anything && e is Anything -> acc
+ acc.lastOrNull() is TextMatcher && e is TextMatcher ->
+ acc.dropLast(1) + TextMatcher((acc.lastOrNull() as TextMatcher).text + e.text)
+ else -> acc + e
+ }
+ }
+ }
+
+ override fun tryMatch(node: ContentNode) {
+ super.tryMatch(node)
+ kclass.cast(node).children.asSequence()
+ .filter { it !is ContentText || it.text.isNotBlank() }
+ .fold(FurtherSiblings(normalizedChildren, this).pop()) { acc, n -> acc.next(n) }.finish()
+ }
+}
+
+public object Anything : MatcherElement()
+
+private sealed class MatchWalkerState {
+ abstract fun next(node: ContentNode): MatchWalkerState
+ abstract fun finish()
+}
+
+private class TextMatcherState(
+ val text: String,
+ val rest: FurtherSiblings,
+ val anchor: TextMatcher
+) : MatchWalkerState() {
+ override fun next(node: ContentNode): MatchWalkerState {
+ node as? ContentText ?: throw MatcherError("Expected text: \"$text\" but got\n${node.debugRepresentation()}", anchor)
+ return when {
+ text == node.text -> rest.pop()
+ text.startsWith(node.text) -> TextMatcherState(text.removePrefix(node.text), rest, anchor)
+ else -> throw MatcherError("Expected text: \"$text\", but got: \"${node.text}\"", anchor)
+ }
+ }
+
+ override fun finish() = throw MatcherError("\"$text\" was not found" + rest.messageEnd, anchor)
+}
+
+private class EmptyMatcherState(val parent: CompositeMatcher<*>) : MatchWalkerState() {
+ override fun next(node: ContentNode): MatchWalkerState {
+ throw MatcherError("Unexpected node:\n${node.debugRepresentation()}", parent, anchorAfter = true)
+ }
+
+ override fun finish() = Unit
+}
+
+private class NodeMatcherState(
+ val matcher: NodeMatcher<*>,
+ val rest: FurtherSiblings
+) : MatchWalkerState() {
+ override fun next(node: ContentNode): MatchWalkerState {
+ matcher.tryMatch(node)
+ return rest.pop()
+ }
+
+ override fun finish() =
+ throw MatcherError("Content of type ${matcher.kclass} was not found" + rest.messageEnd, matcher)
+}
+
+private class SkippingMatcherState(
+ val innerState: MatchWalkerState
+) : MatchWalkerState() {
+ override fun next(node: ContentNode): MatchWalkerState = runCatching { innerState.next(node) }.getOrElse { this }
+
+ override fun finish() = innerState.finish()
+}
+
+private class FurtherSiblings(val list: List<MatcherElement>, val parent: CompositeMatcher<*>) {
+ fun pop(): MatchWalkerState = when (val head = list.firstOrNull()) {
+ is TextMatcher -> TextMatcherState(head.text, drop(), head)
+ is NodeMatcher<*> -> NodeMatcherState(head, drop())
+ is Anything -> SkippingMatcherState(drop().pop())
+ null -> EmptyMatcherState(parent)
+ }
+
+ fun drop() = FurtherSiblings(list.drop(1), parent)
+
+ val messageEnd: String
+ get() = list.count { it !is Anything }.takeIf { it > 0 }
+ ?.let { " and $it further matchers were not satisfied" } ?: ""
+}
+
+
+internal fun MatcherElement.toDebugString(anchor: MatcherElement?, anchorAfter: Boolean): String {
+ fun Appendable.append(element: MatcherElement, ownPrefix: String, childPrefix: String) {
+ if (anchor != null) {
+ if (element != anchor || anchorAfter) append(" ".repeat(4))
+ else append("--> ")
+ }
+
+ append(ownPrefix)
+ when (element) {
+ is Anything -> append("skipAllNotMatching\n")
+ is TextMatcher -> append("\"${element.text}\"\n")
+ is CompositeMatcher<*> -> {
+ append("${element.kclass.simpleName.toString()}\n")
+ if (element.normalizedChildren.isNotEmpty()) {
+ val newOwnPrefix = "$childPrefix├─ "
+ val lastOwnPrefix = "$childPrefix└─ "
+ val newChildPrefix = "$childPrefix│ "
+ val lastChildPrefix = "$childPrefix "
+ element.normalizedChildren.forEachIndexed { n, e ->
+ if (n != element.normalizedChildren.lastIndex) append(e, newOwnPrefix, newChildPrefix)
+ else append(e, lastOwnPrefix, lastChildPrefix)
+ }
+ }
+ if (element == anchor && anchorAfter) {
+ append("--> $childPrefix\n")
+ }
+ }
+ is NodeMatcher<*> -> append("${element.kclass.simpleName}\n")
+ }
+ }
+
+ return buildString { append(this@toDebugString, "", "") }
+}
+
+private fun ContentNode.debugRepresentation() = asPrintableTree { element ->
+ append(if (element is ContentText) """"${element.text}"""" else element::class.simpleName)
+ append(
+ " { " +
+ "kind=${element.dci.kind}, " +
+ "dri=${element.dci.dri}, " +
+ "style=${element.style}, " +
+ "sourceSets=${element.sourceSets} " +
+ "}"
+ )
+}
+
+public data class MatcherError(
+ override val message: String,
+ val anchor: MatcherElement,
+ val anchorAfter: Boolean = false,
+ override val cause: Throwable? = null
+) : AssertionError(message, cause)
+
+// Creating this whole mechanism was most scala-like experience I had since I stopped using scala.
+// I don't know how I should feel about it.