Add support for documenting modules (#1900)

This commit is contained in:
Ara Adkins 2021-07-26 13:26:41 +01:00 committed by GitHub
parent a7478bc573
commit bc96f0e05c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 151 additions and 34 deletions

View File

@ -1,5 +1,10 @@
# Enso Next
## Interpreter/Runtime
- Added support for documenting modules directly
([#1900](https://github.com/enso-org/enso/pull/1900)).
# Enso 0.2.16 (2021-07-23)
## Interpreter/Runtime

View File

@ -89,6 +89,10 @@ for more information). By way of example:
until I unindent again.
```
Documentation blocks are associated with the _next_ entity in the file, except
for if they occur as the _very first_ entity in the file. In this case, they are
treated as the module's documentation.
Documentation comments are _not_ allowed inside textual interpolations.
The tool that generates this documentation aims to be fairly robust, and tries

View File

@ -9,8 +9,8 @@ object Suggestions {
val module: Suggestion.Module = Suggestion.Module(
module = "Test.Main",
documentation = None,
documentationHtml = None
documentation = Some("Module doc"),
documentationHtml = Some("<html></html>")
)
val atom: Suggestion.Atom = Suggestion.Atom(

View File

@ -127,7 +127,13 @@ final class SuggestionBuilder[A: IndexedSource](val source: A) {
}
val builder: TreeBuilder = Vector.newBuilder
builder += Tree.Node(buildModule(module), Vector())
builder += Tree.Node(
buildModule(
module,
ir.getMetadata(DocumentationComments).map(_.documentation)
),
Vector()
)
Tree.Root(
go(builder, Scope(ir.children, ir.location))
@ -205,11 +211,14 @@ final class SuggestionBuilder[A: IndexedSource](val source: A) {
}
/** Build an atom suggestion representing a module. */
private def buildModule(module: QualifiedName): Suggestion =
private def buildModule(
module: QualifiedName,
doc: Option[String]
): Suggestion =
Suggestion.Module(
module = module.toString,
documentation = None,
documentationHtml = None,
documentation = doc,
documentationHtml = doc.map(DocParserWrapper.runOnPureDoc),
reexport = None
)

View File

@ -9,6 +9,9 @@ import org.enso.compiler.pass.IRPass
import org.enso.compiler.pass.desugar.{ComplexType, GenerateMethodBodies}
/** Associates doc comments with the commented entities as metadata.
*
* If the first module definition is a documentation comment, it is treated as
* the module documentation.
*
* This pass has no configuration.
*
@ -159,7 +162,12 @@ case object DocumentationComments extends IRPass {
* @return `ir`, with any doc comments associated with nodes as metadata
*/
private def resolveModule(ir: IR.Module): IR.Module = {
val newBindings = resolveList(ir.bindings).map(resolveDefinition)
val newBindings = (ir.bindings.headOption match {
case Some(doc: IR.Comment.Documentation) =>
ir.updateMetadata(this -->> Doc(doc.doc))
resolveList(ir.bindings.drop(1))
case _ => resolveList(ir.bindings)
}).map(resolveDefinition)
ir.copy(bindings = newBindings)
}

View File

@ -1,7 +1,5 @@
package org.enso.compiler.test.context
import java.util.UUID
import org.enso.compiler.Passes
import org.enso.compiler.context.{
FreshNameSupply,
@ -11,10 +9,13 @@ import org.enso.compiler.context.{
import org.enso.compiler.core.IR
import org.enso.compiler.pass.PassManager
import org.enso.compiler.test.CompilerTest
import org.enso.docs.generator.DocParserWrapper
import org.enso.pkg.QualifiedName
import org.enso.polyglot.Suggestion
import org.enso.polyglot.data.Tree
import java.util.UUID
class SuggestionBuilderTest extends CompilerTest {
implicit val passManager: PassManager = new Passes(defaultConfig).passManager
@ -29,6 +30,16 @@ class SuggestionBuilderTest extends CompilerTest {
),
Vector()
)
private val moduleDoc = "Module doc"
private val DoccedModuleNode = Tree.Node(
Suggestion.Module(
module = Module.toString,
documentation = Some(" " + moduleDoc),
documentationHtml = Some(DocParserWrapper.runOnPureDoc(moduleDoc)),
reexport = None
),
Vector()
)
private def htmlDoc(inner: String): String = {
"<html><body><div class=\"doc\" style=\"font-size: 13px;\"><div><div class=\"\">" + inner + "</div></div></div></body></html>"
@ -68,13 +79,15 @@ class SuggestionBuilderTest extends CompilerTest {
implicit val moduleContext: ModuleContext = freshModuleContext
val code =
"""## The foo
"""## Module doc
|
|## The foo
|foo = 42""".stripMargin
val module = code.preprocessModule
build(code, module) shouldEqual Tree.Root(
Vector(
ModuleNode,
DoccedModuleNode,
Tree.Node(
Suggestion.Method(
externalId = None,
@ -98,14 +111,16 @@ class SuggestionBuilderTest extends CompilerTest {
implicit val moduleContext: ModuleContext = freshModuleContext
val code =
"""## The foo
"""## Module doc
|
|## The foo
|foo : Number
|foo = 42""".stripMargin
val module = code.preprocessModule
build(code, module) shouldEqual Tree.Root(
Vector(
ModuleNode,
DoccedModuleNode,
Tree.Node(
Suggestion.Method(
externalId = None,
@ -1084,13 +1099,15 @@ class SuggestionBuilderTest extends CompilerTest {
implicit val moduleContext: ModuleContext = freshModuleContext
val code =
"""## My sweet type
"""## Module doc
|
|## My sweet type
|type MyType a b""".stripMargin
val module = code.preprocessModule
build(code, module) shouldEqual Tree.Root(
Vector(
ModuleNode,
DoccedModuleNode,
Tree.Node(
Suggestion.Atom(
externalId = None,
@ -1207,7 +1224,9 @@ class SuggestionBuilderTest extends CompilerTest {
implicit val moduleContext: ModuleContext = freshModuleContext
val code =
"""## When in doubt
"""## Module doc
|
|## When in doubt
|type Maybe
| ## Nothing here
| type Nothing
@ -1217,7 +1236,7 @@ class SuggestionBuilderTest extends CompilerTest {
build(code, module) shouldEqual Tree.Root(
Vector(
ModuleNode,
DoccedModuleNode,
Tree.Node(
Suggestion.Atom(
externalId = None,
@ -1808,6 +1827,47 @@ class SuggestionBuilderTest extends CompilerTest {
)
}
"build module with documentation" in {
implicit val moduleContext: ModuleContext = freshModuleContext
val code =
"""## Module doc
|
|## The foo
|foo = 42""".stripMargin
val module = code.preprocessModule
build(code, module) shouldEqual Tree.Root(
Vector(
Tree.Node(
Suggestion.Module(
"Unnamed.Test",
Some(" Module doc"),
Some(
"<html><body><div class=\"doc\" style=\"font-size: 13px;\"><div><div class=\"\"><p>Module doc</p></div></div></div></body></html>"
),
None
),
Vector()
),
Tree.Node(
Suggestion.Method(
externalId = None,
module = "Unnamed.Test",
name = "foo",
arguments = Seq(
Suggestion.Argument("this", "Unnamed.Test", false, false, None)
),
selfType = "Unnamed.Test",
returnType = SuggestionBuilder.Any,
documentation = Some(" The foo"),
documentationHtml = Some(htmlDoc("<p>The foo</p>"))
),
Vector()
)
)
)
}
}
private def build(source: String, ir: IR.Module): Tree.Root[Suggestion] =

View File

@ -175,7 +175,8 @@ class FunctionBindingTest extends CompilerTest {
"retain documentation comments and annotations associated with them" in {
val ir =
s"""
s"""## Module doc
|
|## My documentation for this conversion.
|@My_Annotation
|My_Type.$from (that : Value) = that

View File

@ -80,18 +80,20 @@ class DocumentationCommentsTest extends CompilerTest with Inside {
// === The Tests ============================================================
"Documentation comments in the top scope" should {
"be associated with atoms and methods" in {
implicit val moduleContext: ModuleContext = mkModuleContext
val ir =
"""
|## This is doc for My_Atom
|type My_Atom a b c
|
|## This is doc for my_method
|MyAtom.my_method x = x + this
|
|""".stripMargin.preprocessModule.resolve
implicit val moduleContext: ModuleContext = mkModuleContext
val ir =
"""
|## My module documentation
|
|## This is doc for My_Atom
|type My_Atom a b c
|
|## This is doc for my_method
|MyAtom.my_method x = x + this
|
|""".stripMargin.preprocessModule.resolve
"be associated with atoms and methods" in {
ir.bindings.length shouldEqual 2
ir.bindings(0) shouldBe an[IR.Module.Scope.Definition.Atom]
ir.bindings(1) shouldBe an[IR.Module.Scope.Definition.Method]
@ -99,6 +101,27 @@ class DocumentationCommentsTest extends CompilerTest with Inside {
getDoc(ir.bindings(0)) shouldEqual " This is doc for My_Atom"
getDoc(ir.bindings(1)) shouldEqual " This is doc for my_method"
}
"be associated with modules" in {
getDoc(ir) shouldEqual " My module documentation"
}
"not be associated with modules when not the first entity" in {
implicit val moduleContext: ModuleContext = mkModuleContext
val ir =
"""from Standard.Base import al
|
|## My module documentation
|
|## This is doc for My_Atom
|type My_Atom a b c
|
|## This is doc for my_method
|MyAtom.my_method x = x + this
|""".stripMargin.preprocessModule.resolve
ir.getMetadata(DocumentationComments) should not be defined
}
}
"Documentation comments in blocks" should {
@ -174,7 +197,8 @@ class DocumentationCommentsTest extends CompilerTest with Inside {
implicit val moduleContext: ModuleContext = mkModuleContext
"assign docs to all entities" in {
val ir =
"""
"""## My Module documentation
|
|## the type Foo
|type Foo
| ## the constructor Bar
@ -212,7 +236,9 @@ class DocumentationCommentsTest extends CompilerTest with Inside {
buildModuleContext(freshNameSupply = Some(new FreshNameSupply))
val module =
"""## The foo
"""## Module docs
|
|## The foo
|foo : Integer
|foo = 42""".stripMargin.preprocessModule
val foo = module.bindings.head
@ -227,7 +253,8 @@ class DocumentationCommentsTest extends CompilerTest with Inside {
buildModuleContext(freshNameSupply = Some(new FreshNameSupply))
val ir =
"""
"""## Module Docs
|
|## the type Foo
|type Foo
| ## the constructor Bar

View File

@ -103,7 +103,8 @@ class GenerateDocumentationTest extends CompilerTest with Inside {
"be associated with atoms and methods" in {
implicit val moduleContext: ModuleContext = mkModuleContext
val ir =
"""
"""## Module Docs
|
|## This is doc for My_Atom
|type My_Atom a b c
|

View File

@ -124,7 +124,9 @@ class TypeSignaturesTest extends CompilerTest {
"reattach documentation to method definitions" in {
val ir =
"""## My bar
"""## Module doc
|
|## My bar
|bar : Number -> Number -> Number
|bar a b = a + b
|""".stripMargin.preprocessModule.resolve