From 71cd87e239ba4ba846eb5da04e45a451b8a840cb Mon Sep 17 00:00:00 2001 From: Ilya Ryzhenkov Date: Fri, 3 Oct 2014 22:51:44 +0400 Subject: Resolve links in docs. --- src/Formats/HtmlFormatService.kt | 4 ++ src/Formats/MarkdownFormatService.kt | 6 ++- src/Formats/StructuredFormatService.kt | 5 ++ src/Kotlin/ContentBuilder.kt | 30 ++++++++---- src/Kotlin/CrossReferences.kt | 44 ------------------ src/Kotlin/DocumentationContext.kt | 2 +- src/Kotlin/ResolveReferences.kt | 83 ++++++++++++++++++++++++++++++++++ src/Languages/JavaLanguageService.kt | 25 +++++----- src/Languages/KotlinLanguageService.kt | 29 ++++++------ src/Languages/LanguageService.kt | 17 +++++++ src/Markdown/markdown.bnf | 6 +-- src/Model/Content.kt | 4 +- 12 files changed, 169 insertions(+), 86 deletions(-) delete mode 100644 src/Kotlin/CrossReferences.kt create mode 100644 src/Kotlin/ResolveReferences.kt (limited to 'src') diff --git a/src/Formats/HtmlFormatService.kt b/src/Formats/HtmlFormatService.kt index 06b41518..20cd4f83 100644 --- a/src/Formats/HtmlFormatService.kt +++ b/src/Formats/HtmlFormatService.kt @@ -81,6 +81,10 @@ public open class HtmlFormatService(locationService: LocationService, return "${text}" } + override fun formatLink(text: String, href: String): String { + return "${text}" + } + override fun formatBold(text: String): String { return "${text}" } diff --git a/src/Formats/MarkdownFormatService.kt b/src/Formats/MarkdownFormatService.kt index cb1d713c..bbe0d7be 100644 --- a/src/Formats/MarkdownFormatService.kt +++ b/src/Formats/MarkdownFormatService.kt @@ -35,7 +35,11 @@ public open class MarkdownFormatService(locationService: LocationService, } override public fun formatLink(text: String, location: Location): String { - return "[${text}](${location.path})" + return "[$text](${location.path})" + } + + override fun formatLink(text: String, href: String): String { + return "[$text]($href)" } override public fun appendLine(to: StringBuilder) { diff --git a/src/Formats/StructuredFormatService.kt b/src/Formats/StructuredFormatService.kt index b974dcf8..df11b835 100644 --- a/src/Formats/StructuredFormatService.kt +++ b/src/Formats/StructuredFormatService.kt @@ -25,6 +25,7 @@ public abstract class StructuredFormatService(val locationService: LocationServi public abstract fun formatKeyword(text: String): String public abstract fun formatIdentifier(text: String): String public abstract fun formatLink(text: String, location: Location): String + public abstract fun formatLink(text: String, href: String): String public open fun formatLink(link: FormatLink): String = formatLink(formatText(link.text), link.location) public abstract fun formatBold(text: String): String public abstract fun formatCode(code: String): String @@ -47,6 +48,10 @@ public abstract class StructuredFormatService(val locationService: LocationServi val linkText = formatText(location, content.children) append(formatLink(linkText, linkTo)) } + is ContentExternalLink -> { + val linkText = formatText(location, content.children) + append(formatLink(linkText, content.href)) + } else -> append(formatText(location, content.children)) } }.toString() diff --git a/src/Kotlin/ContentBuilder.kt b/src/Kotlin/ContentBuilder.kt index 78bd7eaf..389b2732 100644 --- a/src/Kotlin/ContentBuilder.kt +++ b/src/Kotlin/ContentBuilder.kt @@ -15,50 +15,60 @@ public fun MarkdownTree.toContent(): Content { MarkdownElementTypes.BULLET_LIST -> { nodeStack.push(ContentList()) processChildren() - parent.children.add(nodeStack.pop()) + parent.append(nodeStack.pop()) } MarkdownElementTypes.ORDERED_LIST -> { nodeStack.push(ContentList()) // TODO: add list kind processChildren() - parent.children.add(nodeStack.pop()) + parent.append(nodeStack.pop()) } MarkdownElementTypes.HORIZONTAL_RULE -> { } MarkdownElementTypes.LIST_BLOCK -> { nodeStack.push(ContentBlock()) processChildren() - parent.children.add(nodeStack.pop()) + parent.append(nodeStack.pop()) } MarkdownElementTypes.EMPH -> { nodeStack.push(ContentEmphasis()) processChildren() - parent.children.add(nodeStack.pop()) + parent.append(nodeStack.pop()) } MarkdownElementTypes.STRONG -> { nodeStack.push(ContentStrong()) processChildren() - parent.children.add(nodeStack.pop()) + parent.append(nodeStack.pop()) } MarkdownElementTypes.ANONYMOUS_SECTION -> { nodeStack.push(ContentSection("")) processChildren() - parent.children.add(nodeStack.pop()) + parent.append(nodeStack.pop()) } MarkdownElementTypes.NAMED_SECTION -> { val label = findChildByType(node, MarkdownElementTypes.SECTION_NAME)?.let { getNodeText(it) } ?: "" nodeStack.push(ContentSection(label)) processChildren() - parent.children.add(nodeStack.pop()) + parent.append(nodeStack.pop()) + } + MarkdownElementTypes.LINK -> { + val target = findChildByType(node, MarkdownElementTypes.TARGET)?.let { getNodeText(it) } ?: "" + val href = findChildByType(node, MarkdownElementTypes.HREF)?.let { getNodeText(it) } + val link = if (href != null) + ContentExternalLink(href) + else + ContentNameLink(target) + link.append(ContentText(target)) + parent.append(link) } MarkdownElementTypes.PLAIN_TEXT -> { nodeStack.push(ContentText(nodeText)) processChildren() - parent.children.add(nodeStack.pop()) + parent.append(nodeStack.pop()) } MarkdownElementTypes.END_LINE -> { nodeStack.push(ContentText(nodeText)) processChildren() - parent.children.add(nodeStack.pop()) + parent.append(nodeStack.pop()) } MarkdownElementTypes.BLANK_LINE -> { processChildren() @@ -66,7 +76,7 @@ public fun MarkdownTree.toContent(): Content { MarkdownElementTypes.PARA -> { nodeStack.push(ContentBlock()) processChildren() - parent.children.add(nodeStack.pop()) + parent.append(nodeStack.pop()) } else -> { processChildren() diff --git a/src/Kotlin/CrossReferences.kt b/src/Kotlin/CrossReferences.kt deleted file mode 100644 index c06b81d4..00000000 --- a/src/Kotlin/CrossReferences.kt +++ /dev/null @@ -1,44 +0,0 @@ -package org.jetbrains.dokka - -/** - * Generates cross-references for documentation such as extensions for a type - * - * $receiver: [DocumentationContext] for node/descriptor resolutions - * $node: [DocumentationNode] to visit - */ -public fun DocumentationContext.buildCrossReferences(node: DocumentationNode) { - node.details(DocumentationNode.Kind.Receiver).forEach { detail -> - val receiverType = detail.detail(DocumentationNode.Kind.Type) - val descriptor = relations[receiverType] - if (descriptor != null) { - val typeNode = descriptorToNode[descriptor] - // if typeNode is null, extension is to external type like in a library - // should we create dummy node here? - typeNode?.addReferenceTo(node, DocumentationReference.Kind.Extension) - } - } - node.details(DocumentationNode.Kind.Supertype).forEach { detail -> - val descriptor = relations[detail] - if (descriptor != null) { - val typeNode = descriptorToNode[descriptor] - typeNode?.addReferenceTo(node, DocumentationReference.Kind.Inheritor) - } - } - node.details.forEach { detail -> - val descriptor = relations[detail] - if (descriptor != null) { - val typeNode = descriptorToNode[descriptor] - if (typeNode != null) { - detail.addReferenceTo(typeNode, DocumentationReference.Kind.Link) - } - } - } - - for (child in node.members) { - buildCrossReferences(child) - } - for (child in node.details) { - buildCrossReferences(child) - } -} - diff --git a/src/Kotlin/DocumentationContext.kt b/src/Kotlin/DocumentationContext.kt index 1fada8b3..649543ac 100644 --- a/src/Kotlin/DocumentationContext.kt +++ b/src/Kotlin/DocumentationContext.kt @@ -54,7 +54,7 @@ fun BindingContext.createDocumentationModule(name: String, pkg!!.accept(DocumentationBuildingVisitor(this, options, visitor), documentationModule) } - context.buildCrossReferences(documentationModule) + context.resolveReferences(documentationModule) // TODO: Uncomment for resolve verification // checkResolveChildren(documentationModule) diff --git a/src/Kotlin/ResolveReferences.kt b/src/Kotlin/ResolveReferences.kt new file mode 100644 index 00000000..90e23ffb --- /dev/null +++ b/src/Kotlin/ResolveReferences.kt @@ -0,0 +1,83 @@ +package org.jetbrains.dokka + +import org.jetbrains.jet.lang.resolve.name.Name + +/** + * Generates cross-references for documentation such as extensions for a type, inheritors, etc + * + * $receiver: [DocumentationContext] for node/descriptor resolutions + * $node: [DocumentationNode] to visit + */ +public fun DocumentationContext.resolveReferences(node: DocumentationNode) { + node.details(DocumentationNode.Kind.Receiver).forEach { detail -> + val receiverType = detail.detail(DocumentationNode.Kind.Type) + val descriptor = relations[receiverType] + if (descriptor != null) { + val typeNode = descriptorToNode[descriptor] + // if typeNode is null, extension is to external type like in a library + // should we create dummy node here? + typeNode?.addReferenceTo(node, DocumentationReference.Kind.Extension) + } + } + node.details(DocumentationNode.Kind.Supertype).forEach { detail -> + val descriptor = relations[detail] + if (descriptor != null) { + val typeNode = descriptorToNode[descriptor] + typeNode?.addReferenceTo(node, DocumentationReference.Kind.Inheritor) + } + } + node.details.forEach { detail -> + val descriptor = relations[detail] + if (descriptor != null) { + val typeNode = descriptorToNode[descriptor] + if (typeNode != null) { + detail.addReferenceTo(typeNode, DocumentationReference.Kind.Link) + } + } + } + + resolveContentLinks(node, node.doc) + + for (child in node.members) { + resolveReferences(child) + } + for (child in node.details) { + resolveReferences(child) + } +} + +fun DocumentationContext.resolveContentLinks(node : DocumentationNode, content: ContentNode) { + val snapshot = content.children.toList() + for (child in snapshot) { + if (child is ContentNameLink) { + val scope = getResolutionScope(node) + val symbolName = child.name + val symbol = + scope.getLocalVariable(Name.guess(symbolName)) ?: + scope.getProperties(Name.guess(symbolName)).firstOrNull() ?: + scope.getFunctions(Name.guess(symbolName)).firstOrNull() ?: + scope.getClassifier(Name.guess(symbolName)) + + if (symbol != null) { + val targetNode = descriptorToNode[symbol] + if (targetNode != null) { + // we have a doc node for the symbol + val index = content.children.indexOf(child) + content.children.remove(index) + val contentLink = ContentNodeLink(targetNode) + contentLink.children.add(ContentIdentifier(symbolName)) + //contentLink.children.addAll(child.children) + content.children.add(index, contentLink) + } else { + // we don't have a doc node for the symbol, render as identifier + val index = content.children.indexOf(child) + content.children.remove(index) + val contentLink = ContentIdentifier(symbolName) + contentLink.children.addAll(child.children) + content.children.add(index, contentLink) + } + } + } + resolveContentLinks(node, child) + } +} \ No newline at end of file diff --git a/src/Languages/JavaLanguageService.kt b/src/Languages/JavaLanguageService.kt index 5d2b48f1..8cc54d4a 100644 --- a/src/Languages/JavaLanguageService.kt +++ b/src/Languages/JavaLanguageService.kt @@ -2,7 +2,10 @@ package org.jetbrains.dokka import org.jetbrains.dokka.DocumentationNode.* -class JavaLanguageService : LanguageService { +/** + * Implements [LanguageService] and provides rendering of symbols in Java language + */ +public class JavaLanguageService : LanguageService { override fun render(node: DocumentationNode): ContentNode { return ContentText(when (node.kind) { Kind.Package -> renderPackage(node) @@ -30,11 +33,11 @@ class JavaLanguageService : LanguageService { } } - fun renderPackage(node: DocumentationNode): String { + private fun renderPackage(node: DocumentationNode): String { return "package ${node.name}" } - fun renderModifier(node: DocumentationNode): String { + private fun renderModifier(node: DocumentationNode): String { return when (node.name) { "open" -> "" "internal" -> "" @@ -42,7 +45,7 @@ class JavaLanguageService : LanguageService { } } - fun renderType(node: DocumentationNode): String { + private fun renderType(node: DocumentationNode): String { return when (node.name) { "Unit" -> "void" "Int" -> "int" @@ -56,7 +59,7 @@ class JavaLanguageService : LanguageService { } } - fun renderTypeParameter(node: DocumentationNode): String { + private fun renderTypeParameter(node: DocumentationNode): String { val constraints = node.details(Kind.UpperBound) return if (constraints.none()) node.name @@ -65,11 +68,11 @@ class JavaLanguageService : LanguageService { } } - fun renderParameter(node: DocumentationNode): String { + private fun renderParameter(node: DocumentationNode): String { return "${renderType(node.detail(Kind.Type))} ${node.name}" } - fun renderTypeParametersForNode(node: DocumentationNode): String { + private fun renderTypeParametersForNode(node: DocumentationNode): String { return StringBuilder { val typeParameters = node.details(Kind.TypeParameter) if (typeParameters.any()) { @@ -80,14 +83,14 @@ class JavaLanguageService : LanguageService { }.toString() } - fun renderModifiersForNode(node: DocumentationNode): String { + private fun renderModifiersForNode(node: DocumentationNode): String { val modifiers = node.details(Kind.Modifier).map { renderModifier(it) }.filter { it != "" } if (modifiers.none()) return "" return modifiers.join(" ", postfix = " ") } - fun renderClass(node: DocumentationNode): String { + private fun renderClass(node: DocumentationNode): String { return StringBuilder { when (node.kind) { Kind.Class -> append("class ") @@ -103,7 +106,7 @@ class JavaLanguageService : LanguageService { }.toString() } - fun renderFunction(node: DocumentationNode): String { + private fun renderFunction(node: DocumentationNode): String { return StringBuilder { when (node.kind) { Kind.Constructor -> append(node.owner?.name) @@ -127,7 +130,7 @@ class JavaLanguageService : LanguageService { }.toString() } - fun renderProperty(node: DocumentationNode): String { + private fun renderProperty(node: DocumentationNode): String { return StringBuilder { when (node.kind) { Kind.Property -> append("val ") diff --git a/src/Languages/KotlinLanguageService.kt b/src/Languages/KotlinLanguageService.kt index 46b58574..f806dd19 100644 --- a/src/Languages/KotlinLanguageService.kt +++ b/src/Languages/KotlinLanguageService.kt @@ -2,6 +2,9 @@ package org.jetbrains.dokka import org.jetbrains.dokka.DocumentationNode.* +/** + * Implements [LanguageService] and provides rendering of symbols in Kotlin language + */ class KotlinLanguageService : LanguageService { override fun render(node: DocumentationNode): ContentNode { return content { @@ -33,13 +36,13 @@ class KotlinLanguageService : LanguageService { } } - fun ContentNode.renderPackage(node: DocumentationNode) { + private fun ContentNode.renderPackage(node: DocumentationNode) { keyword("package") text(" ") identifier(node.name) } - fun ContentNode.renderList(nodes: List, separator: String = ", ", renderItem: (DocumentationNode) -> Unit) { + private fun ContentNode.renderList(nodes: List, separator: String = ", ", renderItem: (DocumentationNode) -> Unit) { if (nodes.none()) return renderItem(nodes.first()) @@ -49,7 +52,7 @@ class KotlinLanguageService : LanguageService { } } - fun ContentNode.renderLinked(node: DocumentationNode, body: ContentNode.(DocumentationNode)->Unit) { + private fun ContentNode.renderLinked(node: DocumentationNode, body: ContentNode.(DocumentationNode)->Unit) { val to = node.links.firstOrNull() if (to == null) body(node) @@ -59,7 +62,7 @@ class KotlinLanguageService : LanguageService { } } - fun ContentNode.renderType(node: DocumentationNode) { + private fun ContentNode.renderType(node: DocumentationNode) { val typeArguments = node.details(Kind.Type) if (node.name == "Function${typeArguments.count() - 1}") { // lambda @@ -99,7 +102,7 @@ class KotlinLanguageService : LanguageService { } } - fun ContentNode.renderModifier(node: DocumentationNode) { + private fun ContentNode.renderModifier(node: DocumentationNode) { when (node.name) { "final", "internal" -> { } @@ -107,7 +110,7 @@ class KotlinLanguageService : LanguageService { } } - fun ContentNode.renderTypeParameter(node: DocumentationNode) { + private fun ContentNode.renderTypeParameter(node: DocumentationNode) { val constraints = node.details(Kind.UpperBound) identifier(node.name) if (constraints.any()) { @@ -118,14 +121,14 @@ class KotlinLanguageService : LanguageService { } } - fun ContentNode.renderParameter(node: DocumentationNode) { + private fun ContentNode.renderParameter(node: DocumentationNode) { identifier(node.name) symbol(": ") val parameterType = node.detail(Kind.Type) renderType(parameterType) } - fun ContentNode.renderTypeParametersForNode(node: DocumentationNode) { + private fun ContentNode.renderTypeParametersForNode(node: DocumentationNode) { val typeParameters = node.details(Kind.TypeParameter) if (typeParameters.any()) { symbol("<") @@ -136,7 +139,7 @@ class KotlinLanguageService : LanguageService { } } - fun ContentNode.renderSupertypesForNode(node: DocumentationNode) { + private fun ContentNode.renderSupertypesForNode(node: DocumentationNode) { val supertypes = node.details(Kind.Supertype) if (supertypes.any()) { symbol(" : ") @@ -146,7 +149,7 @@ class KotlinLanguageService : LanguageService { } } - fun ContentNode.renderModifiersForNode(node: DocumentationNode) { + private fun ContentNode.renderModifiersForNode(node: DocumentationNode) { val modifiers = node.details(Kind.Modifier) for (it in modifiers) { if (node.kind == Kind.Interface && it.name == "abstract") @@ -156,7 +159,7 @@ class KotlinLanguageService : LanguageService { } } - fun ContentNode.renderClass(node: DocumentationNode) { + private fun ContentNode.renderClass(node: DocumentationNode) { renderModifiersForNode(node) when (node.kind) { Kind.Class -> keyword("class ") @@ -172,7 +175,7 @@ class KotlinLanguageService : LanguageService { renderSupertypesForNode(node) } - fun ContentNode.renderFunction(node: DocumentationNode) { + private fun ContentNode.renderFunction(node: DocumentationNode) { renderModifiersForNode(node) when (node.kind) { Kind.Constructor -> identifier(node.owner!!.name) @@ -200,7 +203,7 @@ class KotlinLanguageService : LanguageService { } } - fun ContentNode.renderProperty(node: DocumentationNode) { + private fun ContentNode.renderProperty(node: DocumentationNode) { renderModifiersForNode(node) when (node.kind) { Kind.Property -> keyword("val ") diff --git a/src/Languages/LanguageService.kt b/src/Languages/LanguageService.kt index 26217d41..bf55ac2c 100644 --- a/src/Languages/LanguageService.kt +++ b/src/Languages/LanguageService.kt @@ -1,7 +1,24 @@ package org.jetbrains.dokka +/** + * Provides facility for rendering [DocumentationNode] as a language-dependent declaration + */ trait LanguageService { + /** + * Renders [node] as a class, function, property or other signature + * $node: A [DocumentationNode] to render + * $returns: [ContentNode] which is a root for a rich content tree suitable for formatting with [FormatService] + */ fun render(node: DocumentationNode): ContentNode + + /** + * Renders [node] as a named representation in the target language + * + * See also [google](http://google.com) + * + * $node: A [DocumentationNode] to render + * $returns: [String] which is a string representation of the node + */ fun renderName(node: DocumentationNode) : String } diff --git a/src/Markdown/markdown.bnf b/src/Markdown/markdown.bnf index d26b18a6..d6fd2ed2 100644 --- a/src/Markdown/markdown.bnf +++ b/src/Markdown/markdown.bnf @@ -86,8 +86,6 @@ Strong ::= StrongStar | StrongUnderscore StrongStar ::= '**' !Whitespace (!'**' Inline)+ '**' StrongUnderscore ::= '__' !Whitespace (!'__' Inline)+ '__' -Link ::= HrefLink | ReferenceLink -private ReferenceLink ::= '[' Target ']' -private HrefLink ::= '[' Target ']' '(' Href ')' +Link ::= '[' Target ']' ('(' Href ')')? Target ::= Word+ -Href ::= Word+ \ No newline at end of file +Href ::= (Word | Number | ':' | '/')+ \ No newline at end of file diff --git a/src/Model/Content.kt b/src/Model/Content.kt index 4b92a958..27adfe59 100644 --- a/src/Model/Content.kt +++ b/src/Model/Content.kt @@ -9,9 +9,8 @@ public abstract class ContentNode { val empty = ContentEmpty } - fun append(node : ContentNode) : ContentNode { + fun append(node : ContentNode) { children.add(node) - return this } fun isEmpty() = children.isEmpty() @@ -26,6 +25,7 @@ public class ContentIdentifier(val text: String) : ContentNode() public class ContentSymbol(val text: String) : ContentNode() public class ContentEmphasis() : ContentBlock() public class ContentNodeLink(val node : DocumentationNode) : ContentBlock() +public class ContentNameLink(val name : String) : ContentBlock() public class ContentExternalLink(val href : String) : ContentBlock() public class ContentStrong() : ContentBlock() public class ContentList() : ContentBlock() -- cgit