diff --git a/RELEASES.md b/RELEASES.md index 7aa7a01003b8..22c9094e565b 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -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 diff --git a/docs/syntax/comments.md b/docs/syntax/comments.md index 79c2e422a293..5680c94d9a13 100644 --- a/docs/syntax/comments.md +++ b/docs/syntax/comments.md @@ -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 diff --git a/engine/language-server/src/test/scala/org/enso/languageserver/search/Suggestions.scala b/engine/language-server/src/test/scala/org/enso/languageserver/search/Suggestions.scala index 0923f252ad81..4cd242504814 100644 --- a/engine/language-server/src/test/scala/org/enso/languageserver/search/Suggestions.scala +++ b/engine/language-server/src/test/scala/org/enso/languageserver/search/Suggestions.scala @@ -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("") ) val atom: Suggestion.Atom = Suggestion.Atom( diff --git a/engine/runtime/src/main/scala/org/enso/compiler/context/SuggestionBuilder.scala b/engine/runtime/src/main/scala/org/enso/compiler/context/SuggestionBuilder.scala index 7e6b1be2aff9..3728d64fe357 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/context/SuggestionBuilder.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/context/SuggestionBuilder.scala @@ -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 ) diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/DocumentationComments.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/DocumentationComments.scala index e4665de84934..d5e71b71a801 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/DocumentationComments.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/DocumentationComments.scala @@ -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) } diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/context/SuggestionBuilderTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/context/SuggestionBuilderTest.scala index 62c5e3df39c0..343422e52b47 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/context/SuggestionBuilderTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/context/SuggestionBuilderTest.scala @@ -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 = { "
Module doc
The foo
")) + ), + Vector() + ) + ) + ) + } } private def build(source: String, ir: IR.Module): Tree.Root[Suggestion] = diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/desugar/FunctionBindingTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/desugar/FunctionBindingTest.scala index 8ab592f15fe4..e149f2b80e8d 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/desugar/FunctionBindingTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/desugar/FunctionBindingTest.scala @@ -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 diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/DocumentationCommentsTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/DocumentationCommentsTest.scala index 1657d29b62ea..4c3d65755bf8 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/DocumentationCommentsTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/DocumentationCommentsTest.scala @@ -80,24 +80,47 @@ class DocumentationCommentsTest extends CompilerTest with Inside { // === The Tests ============================================================ "Documentation comments in the top scope" should { + 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] + + 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.bindings.length shouldEqual 2 - ir.bindings(0) shouldBe an[IR.Module.Scope.Definition.Atom] - ir.bindings(1) shouldBe an[IR.Module.Scope.Definition.Method] - - getDoc(ir.bindings(0)) shouldEqual " This is doc for My_Atom" - getDoc(ir.bindings(1)) shouldEqual " This is doc for my_method" + ir.getMetadata(DocumentationComments) should not be defined } } @@ -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 diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/GenerateDocumentationTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/GenerateDocumentationTest.scala index 5a9b5e52e09e..1b08c28d9624 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/GenerateDocumentationTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/GenerateDocumentationTest.scala @@ -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 | diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/TypeSignaturesTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/TypeSignaturesTest.scala index b8ff089929af..728e58cca9a7 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/TypeSignaturesTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/TypeSignaturesTest.scala @@ -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