From 2cd880f43d175bf73e97754db3654c037cdcf106 Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Tue, 10 Jan 2023 19:59:53 +0300 Subject: [PATCH] Documentation for functions and locals (#4029) Add documentation for functions and locals to suggestions database. --- .../protocol-language-server.md | 23 +++ .../search/SearchProtocol.scala | 20 +-- .../search/SuggestionsHandler.scala | 30 +++- .../languageserver/search/Suggestions.scala | 8 +- .../json/SuggestionsHandlerEventsTest.scala | 18 ++- .../scala/org/enso/polyglot/Suggestion.scala | 50 +++--- .../test/instrument/RuntimeStdlibTest.scala | 10 +- .../RuntimeSuggestionUpdatesTest.scala | 24 ++- .../compiler/context/SuggestionBuilder.scala | 28 ++-- .../compiler/context/SuggestionDiff.scala | 6 + .../test/context/SuggestionBuilderTest.scala | 148 ++++++++++++++++-- .../enso/searcher/sql/SuggestionRandom.scala | 24 +-- .../searcher/sql/SqlSuggestionsRepo.scala | 28 +++- .../searcher/sql/SuggestionsRepoTest.scala | 75 ++++++++- 14 files changed, 396 insertions(+), 96 deletions(-) diff --git a/docs/language-server/protocol-language-server.md b/docs/language-server/protocol-language-server.md index c09acfffef..9ef7aeab91 100644 --- a/docs/language-server/protocol-language-server.md +++ b/docs/language-server/protocol-language-server.md @@ -602,6 +602,15 @@ interface Function { /** The scope where the function is defined. */ scope: SuggestionEntryScope; + + /** The documentation string. */ + documentation?: string; + + /** The rendered HTML of the documentation string. */ + documentationHtml?: string; + + /** The documentation string divided into sections. */ + documentationSections?: DocSection[]; } interface Local { @@ -619,6 +628,15 @@ interface Local { /** The scope where the value is defined. */ scope: SuggestionEntryScope; + + /** The documentation string. */ + documentation?: string; + + /** The rendered HTML of the documentation string. */ + documentationHtml?: string; + + /** The documentation string divided into sections. */ + documentationSections?: DocSection[]; } ``` @@ -940,6 +958,11 @@ interface Modify { */ documentation?: FieldUpdate; + /** + * New documentation sections. + */ + documentationSections?: FieldUpdate; + /** * The scope to update. */ diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/search/SearchProtocol.scala b/engine/language-server/src/main/scala/org/enso/languageserver/search/SearchProtocol.scala index 984dab06c5..5d80626e00 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/search/SearchProtocol.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/search/SearchProtocol.scala @@ -408,20 +408,22 @@ object SearchProtocol { * @param returnType the return type to update * @param documentation the documentation string to update * @param documentationHtml the HTML documentation to update + * @param documentationSections the documentation sections to update * @param scope the scope to update * @param reexport the module reexporting the suggestion */ case class Modify( id: SuggestionId, - externalId: Option[FieldUpdate[Suggestion.ExternalId]] = None, - arguments: Option[Seq[SuggestionArgumentUpdate]] = None, - module: Option[FieldUpdate[String]] = None, - selfType: Option[FieldUpdate[String]] = None, - returnType: Option[FieldUpdate[String]] = None, - documentation: Option[FieldUpdate[String]] = None, - documentationHtml: Option[FieldUpdate[String]] = None, - scope: Option[FieldUpdate[Suggestion.Scope]] = None, - reexport: Option[FieldUpdate[String]] = None + externalId: Option[FieldUpdate[Suggestion.ExternalId]] = None, + arguments: Option[Seq[SuggestionArgumentUpdate]] = None, + module: Option[FieldUpdate[String]] = None, + selfType: Option[FieldUpdate[String]] = None, + returnType: Option[FieldUpdate[String]] = None, + documentation: Option[FieldUpdate[String]] = None, + documentationHtml: Option[FieldUpdate[String]] = None, + documentationSections: Option[FieldUpdate[Seq[DocSection]]] = None, + scope: Option[FieldUpdate[Suggestion.Scope]] = None, + reexport: Option[FieldUpdate[String]] = None ) extends SuggestionsDatabaseUpdate implicit val decoder: Decoder[SuggestionsDatabaseUpdate] = diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/search/SuggestionsHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/search/SuggestionsHandler.scala index a3b1c7830f..7a9a0d3998 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/search/SuggestionsHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/search/SuggestionsHandler.scala @@ -591,8 +591,11 @@ final class SuggestionsHandler( externalId = m.externalId.map(fieldUpdateOption), arguments = m.arguments.map(_.map(toApiArgumentAction)), returnType = m.returnType.map(fieldUpdate), + scope = m.scope.map(fieldUpdate), documentation = m.documentation.map(fieldUpdateOption), - scope = m.scope.map(fieldUpdate) + documentationSections = m.documentation.map( + fieldUpdateMapOption(docSectionsBuilder.build) + ) ) } } @@ -625,7 +628,7 @@ final class SuggestionsHandler( /** Construct the field update object from an optional value. * * @param value the optional value - * @return the field update object representint the value update + * @return the field update object representing the value update */ private def fieldUpdateOption[A](value: Option[A]): FieldUpdate[A] = value match { @@ -633,6 +636,20 @@ final class SuggestionsHandler( case None => FieldUpdate(FieldAction.Remove, None) } + /** Construct the field update object from an optional value. + * + * @param f the mapping function + * @param value the optional value + * @return the field update object representing the value update + */ + private def fieldUpdateMapOption[A, B]( + f: A => B + )(value: Option[A]): FieldUpdate[B] = + value match { + case Some(value) => FieldUpdate(FieldAction.Set, Some(f(value))) + case None => FieldUpdate(FieldAction.Remove, None) + } + /** Construct the field update object from and update value. * * @param value the update value @@ -731,8 +748,13 @@ final class SuggestionsHandler( val docSections = conversion.documentation.map(docSectionsBuilder.build) conversion.copy(documentationSections = docSections) - case _: Suggestion.Function => suggestion - case _: Suggestion.Local => suggestion + case function: Suggestion.Function => + val docSections = function.documentation.map(docSectionsBuilder.build) + function.copy(documentationSections = docSections) + + case local: Suggestion.Local => + val docSections = local.documentation.map(docSectionsBuilder.build) + local.copy(documentationSections = docSections) } } 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 1ae79a108e..88a5b97d1f 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 @@ -87,7 +87,10 @@ object Suggestions { ), returnType = "IO", scope = - Suggestion.Scope(Suggestion.Position(1, 9), Suggestion.Position(1, 22)) + Suggestion.Scope(Suggestion.Position(1, 9), Suggestion.Position(1, 22)), + documentation = Some("My Function"), + documentationHtml = None, + documentationSections = Some(docSectionsBuilder.build("My Function")) ) val local: Suggestion.Local = Suggestion.Local( @@ -96,7 +99,8 @@ object Suggestions { name = "x", returnType = "Number", scope = - Suggestion.Scope(Suggestion.Position(21, 0), Suggestion.Position(89, 0)) + Suggestion.Scope(Suggestion.Position(21, 0), Suggestion.Position(89, 0)), + documentation = None ) val methodOnAny: Suggestion.Method = Suggestion.Method( diff --git a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/SuggestionsHandlerEventsTest.scala b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/SuggestionsHandlerEventsTest.scala index 75b2bcf90b..4fa2728305 100644 --- a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/SuggestionsHandlerEventsTest.scala +++ b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/SuggestionsHandlerEventsTest.scala @@ -307,7 +307,14 @@ class SuggestionsHandlerEventsTest extends BaseServerTest with FlakySpec { "line" : 1, "character" : 22 } - } + }, + "documentation" : "My Function", + "documentationSections" : [ + { + "type" : "paragraph", + "body" : "My Function" + } + ] } } ], @@ -503,7 +510,14 @@ class SuggestionsHandlerEventsTest extends BaseServerTest with FlakySpec { "line" : 1, "character" : 22 } - } + }, + "documentation" : "My Function", + "documentationSections" : [ + { + "type" : "paragraph", + "body" : "My Function" + } + ] } }, { diff --git a/engine/polyglot-api/src/main/scala/org/enso/polyglot/Suggestion.scala b/engine/polyglot-api/src/main/scala/org/enso/polyglot/Suggestion.scala index f80e57f8a1..42b0cf13ac 100644 --- a/engine/polyglot-api/src/main/scala/org/enso/polyglot/Suggestion.scala +++ b/engine/polyglot-api/src/main/scala/org/enso/polyglot/Suggestion.scala @@ -41,10 +41,11 @@ import java.util.UUID ) sealed trait Suggestion extends ToLogString { - def externalId: Option[Suggestion.ExternalId] - def module: String - def name: String - def returnType: String + def externalId: Option[Suggestion.ExternalId] + def module: String + def name: String + def returnType: String + def documentation: Option[String] } object Suggestion { @@ -121,20 +122,6 @@ object Suggestion { } } - /** Documentation extractor */ - object Documentation { - def apply(suggestion: Suggestion): Option[String] = - suggestion match { - case module: Module => module.documentation - case tpe: Type => tpe.documentation - case constructor: Constructor => constructor.documentation - case method: Method => method.documentation - case conv: Conversion => conv.documentation - case _: Function => None - case _: Local => None - } - } - /** An argument of an atom or a function. * * @param name the argument name @@ -185,6 +172,7 @@ object Suggestion { * @param module the fully qualified module name * @param documentation the documentation string * @param documentationHtml the documentation rendered as HTML + * @param documentationSections the documentation parsed into sections * @param reexport the module re-exporting this module */ case class Module( @@ -222,6 +210,7 @@ object Suggestion { * @param parentType qualified name of the parent type * @param documentation the documentation string * @param documentationHtml the documentation rendered as HTML + * @param documentationSections the documentation parsed into sections * @param reexport the module re-exporting this atom */ case class Type( @@ -261,6 +250,7 @@ object Suggestion { * @param returnType the type of an atom * @param documentation the documentation string * @param documentationHtml the documentation rendered as HTML + * @param documentationSections the documentation parsed into sections * @param reexport the module re-exporting this atom */ case class Constructor( @@ -300,6 +290,7 @@ object Suggestion { * @param isStatic the flag indicating whether a method is static or instance * @param documentation the documentation string * @param documentationHtml the documentation rendered as HTML + * @param documentationSections the documentation parsed into sections * @param reexport the module re-exporting this method */ case class Method( @@ -340,6 +331,7 @@ object Suggestion { * @param returnType the return type of a conversion * @param documentation the documentation string * @param documentationHtml the documentation rendered as HTML + * @param documentationSections the documentation parsed into sections * @param reexport the module re-exporting this conversion */ case class Conversion( @@ -378,6 +370,9 @@ object Suggestion { * @param arguments the function arguments * @param returnType the return type of a function * @param scope the scope where the function is defined + * @param documentation the documentation string + * @param documentationHtml the documentation rendered as HTML + * @param documentationSections the documentation parsed into sections */ case class Function( externalId: Option[ExternalId], @@ -385,7 +380,10 @@ object Suggestion { name: String, arguments: Seq[Argument], returnType: String, - scope: Scope + scope: Scope, + documentation: Option[String], + documentationHtml: Option[String] = None, + documentationSections: Option[List[DocSection]] = None ) extends Suggestion with ToLogString { @@ -397,7 +395,8 @@ object Suggestion { s"name=$name," + s"arguments=${arguments.map(_.toLogString(shouldMask))}," + s"returnType=$returnType," + - s"scope=$scope" + + s"scope=$scope," + + s"documentation=$documentation" + ")" } @@ -408,13 +407,19 @@ object Suggestion { * @param name the name of a value * @param returnType the type of a local value * @param scope the scope where the value is defined + * @param documentation the documentation string + * @param documentationHtml the documentation rendered as HTML + * @param documentationSections the documentation parsed into sections */ case class Local( externalId: Option[ExternalId], module: String, name: String, returnType: String, - scope: Scope + scope: Scope, + documentation: Option[String], + documentationHtml: Option[String] = None, + documentationSections: Option[List[DocSection]] = None ) extends Suggestion { /** @inheritdoc */ @@ -424,7 +429,8 @@ object Suggestion { s"module=$module," + s"name=$name," + s"returnType=$returnType," + - s"scope=$scope" + + s"scope=$scope," + + s"documentation=$documentation" + s")" } } diff --git a/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeStdlibTest.scala b/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeStdlibTest.scala index 373a86a2f0..a68481331c 100644 --- a/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeStdlibTest.scala +++ b/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeStdlibTest.scala @@ -360,10 +360,12 @@ class RuntimeStdlibTest ) ) => updates.toVector.foreach { update => - val docstring = Suggestion.Documentation(update.suggestion) - docstring.foreach( - context.docsGenerator.generate(_, update.suggestion.name) - ) + update.suggestion.documentation.foreach { documentation => + context.docsGenerator.generate( + documentation, + update.suggestion.name + ) + } } } } diff --git a/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeSuggestionUpdatesTest.scala b/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeSuggestionUpdatesTest.scala index 6084096288..e51727e724 100644 --- a/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeSuggestionUpdatesTest.scala +++ b/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeSuggestionUpdatesTest.scala @@ -247,7 +247,8 @@ class RuntimeSuggestionUpdatesTest Suggestion.Scope( Suggestion.Position(2, 6), Suggestion.Position(4, 16) - ) + ), + None ), Api.SuggestionAction.Add() ), @@ -328,7 +329,8 @@ class RuntimeSuggestionUpdatesTest Suggestion.Scope( Suggestion.Position(2, 6), Suggestion.Position(4, 16) - ) + ), + None ), Api.SuggestionAction.Modify(scope = Some( @@ -351,7 +353,8 @@ class RuntimeSuggestionUpdatesTest Suggestion.Scope( Suggestion.Position(2, 6), Suggestion.Position(5, 18) - ) + ), + None ), Api.SuggestionAction.Add() ), @@ -429,7 +432,8 @@ class RuntimeSuggestionUpdatesTest Suggestion.Scope( Suggestion.Position(2, 6), Suggestion.Position(5, 18) - ) + ), + None ), Api.SuggestionAction.Modify(scope = Some( @@ -452,7 +456,8 @@ class RuntimeSuggestionUpdatesTest Suggestion.Scope( Suggestion.Position(2, 6), Suggestion.Position(5, 18) - ) + ), + None ), Api.SuggestionAction.Modify( returnType = Some(ConstantsGen.NUMBER), @@ -540,7 +545,8 @@ class RuntimeSuggestionUpdatesTest Suggestion.Scope( Suggestion.Position(2, 6), Suggestion.Position(6, 18) - ) + ), + None ), Api.SuggestionAction.Modify(scope = Some( @@ -563,7 +569,8 @@ class RuntimeSuggestionUpdatesTest Suggestion.Scope( Suggestion.Position(2, 6), Suggestion.Position(6, 18) - ) + ), + None ), Api.SuggestionAction.Modify(scope = Some( @@ -808,7 +815,8 @@ class RuntimeSuggestionUpdatesTest Suggestion.Scope( Suggestion.Position(4, 6), Suggestion.Position(8, 11) - ) + ), + None ), Api.SuggestionAction.Add() ), 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 8afbe01216..ce510cd692 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 @@ -165,6 +165,7 @@ final class SuggestionBuilder[A: IndexedSource]( name, args, scope.location.get, + doc, typeSignature ) val subforest = go( @@ -181,6 +182,7 @@ final class SuggestionBuilder[A: IndexedSource]( module, name.name, scope.location.get, + doc, typeSignature ) val subforest = go( @@ -264,18 +266,20 @@ final class SuggestionBuilder[A: IndexedSource]( name: IR.Name, args: Seq[IR.DefinitionArgument], location: Location, + doc: Option[String], typeSignature: Option[TypeSignatures.Metadata] ): Suggestion.Function = { val typeSig = buildTypeSignatureFromMetadata(typeSignature) val (methodArgs, returnTypeDef) = buildFunctionArguments(args, typeSig) Suggestion.Function( - externalId = externalId, - module = module.toString, - name = name.name, - arguments = methodArgs, - returnType = buildReturnType(returnTypeDef), - scope = buildScope(location) + externalId = externalId, + module = module.toString, + name = name.name, + arguments = methodArgs, + returnType = buildReturnType(returnTypeDef), + scope = buildScope(location), + documentation = doc ) } @@ -285,16 +289,18 @@ final class SuggestionBuilder[A: IndexedSource]( module: QualifiedName, name: String, location: Location, + doc: Option[String], typeSignature: Option[TypeSignatures.Metadata] ): Suggestion.Local = { val typeSig = buildTypeSignatureFromMetadata(typeSignature) val (_, returnTypeDef) = buildFunctionArguments(Seq(), typeSig) Suggestion.Local( - externalId, - module.toString, - name, - buildReturnType(returnTypeDef), - buildScope(location) + externalId = externalId, + module = module.toString, + name = name, + returnType = buildReturnType(returnTypeDef), + scope = buildScope(location), + documentation = doc ) } diff --git a/engine/runtime/src/main/scala/org/enso/compiler/context/SuggestionDiff.scala b/engine/runtime/src/main/scala/org/enso/compiler/context/SuggestionDiff.scala index b641ea2336..4027544786 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/context/SuggestionDiff.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/context/SuggestionDiff.scala @@ -222,6 +222,9 @@ object SuggestionDiff { if (e1.scope != e2.scope) { op = op.copy(scope = Some(e2.scope)) } + if (e1.documentation != e2.documentation) { + op = op.copy(documentation = Some(e2.documentation)) + } Api.SuggestionUpdate(e1, op) } @@ -239,6 +242,9 @@ object SuggestionDiff { if (e1.scope != e2.scope) { op = op.copy(scope = Some(e2.scope)) } + if (e1.documentation != e2.documentation) { + op = op.copy(documentation = Some(e2.documentation)) + } Api.SuggestionUpdate(e1, op) } } 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 aa073aac47..8766be0f77 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 @@ -317,7 +317,11 @@ class SuggestionBuilderTest extends AnyWordSpecLike with Matchers { "x", "Number", Suggestion - .Scope(Suggestion.Position(0, 9), Suggestion.Position(4, 9)) + .Scope( + Suggestion.Position(0, 9), + Suggestion.Position(4, 9) + ), + None ), Vector() ), @@ -328,7 +332,11 @@ class SuggestionBuilderTest extends AnyWordSpecLike with Matchers { "y", SuggestionBuilder.Any, Suggestion - .Scope(Suggestion.Position(0, 9), Suggestion.Position(4, 9)) + .Scope( + Suggestion.Position(0, 9), + Suggestion.Position(4, 9) + ), + None ), Vector() ) @@ -939,7 +947,8 @@ class SuggestionBuilderTest extends AnyWordSpecLike with Matchers { scope = Suggestion.Scope( Suggestion.Position(0, 6), Suggestion.Position(2, 10) - ) + ), + None ), Vector() ) @@ -988,7 +997,8 @@ class SuggestionBuilderTest extends AnyWordSpecLike with Matchers { scope = Suggestion.Scope( Suggestion.Position(0, 6), Suggestion.Position(4, 10) - ) + ), + documentation = None ), Vector( Tree.Node( @@ -1000,7 +1010,8 @@ class SuggestionBuilderTest extends AnyWordSpecLike with Matchers { scope = Suggestion.Scope( Suggestion.Position(1, 11), Suggestion.Position(3, 9) - ) + ), + documentation = None ), Vector() ) @@ -1049,7 +1060,8 @@ class SuggestionBuilderTest extends AnyWordSpecLike with Matchers { scope = Suggestion.Scope( Suggestion.Position(0, 6), Suggestion.Position(3, 10) - ) + ), + documentation = None ), Vector() ) @@ -1118,7 +1130,63 @@ class SuggestionBuilderTest extends AnyWordSpecLike with Matchers { scope = Suggestion.Scope( Suggestion.Position(2, 6), Suggestion.Position(5, 10) - ) + ), + documentation = None + ), + Vector() + ) + ) + ) + ) + ) + } + + "build function with documentation" in { + + val code = + """main = + | ## Foo documentation. + | foo a = a + 1 + | foo 42 + |""".stripMargin + val module = code.preprocessModule + + build(code, module) shouldEqual Tree.Root( + Vector( + ModuleNode, + Tree.Node( + Suggestion.Method( + externalId = None, + module = "Unnamed.Test", + name = "main", + arguments = Seq(), + selfType = "Unnamed.Test", + returnType = SuggestionBuilder.Any, + isStatic = true, + documentation = None + ), + Vector( + Tree.Node( + Suggestion.Function( + externalId = None, + module = "Unnamed.Test", + name = "foo", + arguments = Seq( + Suggestion + .Argument( + "a", + SuggestionBuilder.Any, + false, + false, + None + ) + ), + returnType = SuggestionBuilder.Any, + scope = Suggestion.Scope( + Suggestion.Position(0, 6), + Suggestion.Position(3, 10) + ), + documentation = Some(" Foo documentation.") ), Vector() ) @@ -1161,7 +1229,8 @@ class SuggestionBuilderTest extends AnyWordSpecLike with Matchers { scope = Suggestion.Scope( Suggestion.Position(0, 6), Suggestion.Position(2, 7) - ) + ), + documentation = None ), Vector() ) @@ -1206,7 +1275,8 @@ class SuggestionBuilderTest extends AnyWordSpecLike with Matchers { scope = Suggestion.Scope( Suggestion.Position(0, 6), Suggestion.Position(4, 7) - ) + ), + documentation = None ), Vector( Tree.Node( @@ -1218,7 +1288,8 @@ class SuggestionBuilderTest extends AnyWordSpecLike with Matchers { scope = Suggestion.Scope( Suggestion.Position(1, 9), Suggestion.Position(3, 9) - ) + ), + documentation = None ), Vector() ) @@ -1264,7 +1335,8 @@ class SuggestionBuilderTest extends AnyWordSpecLike with Matchers { scope = Suggestion.Scope( Suggestion.Position(0, 6), Suggestion.Position(3, 7) - ) + ), + documentation = None ), Vector() ) @@ -1322,7 +1394,53 @@ class SuggestionBuilderTest extends AnyWordSpecLike with Matchers { scope = Suggestion.Scope( Suggestion.Position(2, 6), Suggestion.Position(5, 7) - ) + ), + documentation = None + ), + Vector() + ) + ) + ) + ) + ) + } + + "build local with documentation" in { + + val code = + """main = + | ## This is foo. + | foo = 42 + | foo + |""".stripMargin + val module = code.preprocessModule + + build(code, module) shouldEqual Tree.Root( + Vector( + ModuleNode, + Tree.Node( + Suggestion.Method( + externalId = None, + module = "Unnamed.Test", + name = "main", + arguments = Seq(), + selfType = "Unnamed.Test", + returnType = SuggestionBuilder.Any, + isStatic = true, + documentation = None + ), + Vector( + Tree.Node( + Suggestion.Local( + externalId = None, + module = "Unnamed.Test", + name = "foo", + returnType = SuggestionBuilder.Any, + scope = Suggestion.Scope( + Suggestion.Position(0, 6), + Suggestion.Position(3, 7) + ), + documentation = Some(" This is foo.") ), Vector() ) @@ -2187,7 +2305,8 @@ class SuggestionBuilderTest extends AnyWordSpecLike with Matchers { scope = Suggestion.Scope( Suggestion.Position(0, 6), Suggestion.Position(2, 28) - ) + ), + documentation = None ), Vector() ) @@ -2236,7 +2355,8 @@ class SuggestionBuilderTest extends AnyWordSpecLike with Matchers { scope = Suggestion.Scope( Suggestion.Position(0, 6), Suggestion.Position(2, 18) - ) + ), + documentation = None ), Vector() ) diff --git a/lib/scala/searcher/src/bench/scala/org/enso/searcher/sql/SuggestionRandom.scala b/lib/scala/searcher/src/bench/scala/org/enso/searcher/sql/SuggestionRandom.scala index 6affadc288..bd0bf133a9 100644 --- a/lib/scala/searcher/src/bench/scala/org/enso/searcher/sql/SuggestionRandom.scala +++ b/lib/scala/searcher/src/bench/scala/org/enso/searcher/sql/SuggestionRandom.scala @@ -77,21 +77,23 @@ object SuggestionRandom { def nextSuggestionFunction(): Suggestion.Function = Suggestion.Function( - externalId = optional(UUID.randomUUID()), - module = "Test.Main", - name = nextString(), - arguments = Seq(), - returnType = nextString(), - scope = nextScope() + externalId = optional(UUID.randomUUID()), + module = "Test.Main", + name = nextString(), + arguments = Seq(), + returnType = nextString(), + scope = nextScope(), + documentation = optional(nextString()) ) def nextSuggestionLocal(): Suggestion.Local = Suggestion.Local( - externalId = optional(UUID.randomUUID()), - module = "Test.Main", - name = nextString(), - returnType = nextString(), - scope = nextScope() + externalId = optional(UUID.randomUUID()), + module = "Test.Main", + name = nextString(), + returnType = nextString(), + scope = nextScope(), + documentation = optional(nextString()) ) def nextScope(): Suggestion.Scope = diff --git a/lib/scala/searcher/src/main/scala/org/enso/searcher/sql/SqlSuggestionsRepo.scala b/lib/scala/searcher/src/main/scala/org/enso/searcher/sql/SqlSuggestionsRepo.scala index c672b1141e..aa817af6fc 100644 --- a/lib/scala/searcher/src/main/scala/org/enso/searcher/sql/SqlSuggestionsRepo.scala +++ b/lib/scala/searcher/src/main/scala/org/enso/searcher/sql/SqlSuggestionsRepo.scala @@ -1129,7 +1129,17 @@ final class SqlSuggestionsRepo(val db: SqlDatabase)(implicit reexport = reexport ) row -> (firstArg +: args) - case Suggestion.Function(expr, module, name, args, returnType, scope) => + case Suggestion.Function( + expr, + module, + name, + args, + returnType, + scope, + doc, + _, + _ + ) => val row = SuggestionRow( id = None, externalIdLeast = expr.map(_.getLeastSignificantBits), @@ -1141,7 +1151,7 @@ final class SqlSuggestionsRepo(val db: SqlDatabase)(implicit returnType = returnType, parentType = None, isStatic = false, - documentation = None, + documentation = doc, scopeStartLine = scope.start.line, scopeStartOffset = scope.start.character, scopeEndLine = scope.end.line, @@ -1149,7 +1159,7 @@ final class SqlSuggestionsRepo(val db: SqlDatabase)(implicit reexport = None ) row -> args - case Suggestion.Local(expr, module, name, returnType, scope) => + case Suggestion.Local(expr, module, name, returnType, scope, doc, _, _) => val row = SuggestionRow( id = None, externalIdLeast = expr.map(_.getLeastSignificantBits), @@ -1161,7 +1171,7 @@ final class SqlSuggestionsRepo(val db: SqlDatabase)(implicit returnType = returnType, parentType = None, isStatic = false, - documentation = None, + documentation = doc, scopeStartLine = scope.start.line, scopeStartOffset = scope.start.character, scopeEndLine = scope.end.line, @@ -1288,7 +1298,10 @@ final class SqlSuggestionsRepo(val db: SqlDatabase)(implicit suggestion.scopeEndLine, suggestion.scopeEndOffset ) - ) + ), + documentation = suggestion.documentation, + documentationHtml = None, + documentationSections = None ) case SuggestionKind.LOCAL => Suggestion.Local( @@ -1306,7 +1319,10 @@ final class SqlSuggestionsRepo(val db: SqlDatabase)(implicit suggestion.scopeEndLine, suggestion.scopeEndOffset ) - ) + ), + documentation = suggestion.documentation, + documentationHtml = None, + documentationSections = None ) case k => throw new NoSuchElementException(s"Unknown suggestion kind: $k") diff --git a/lib/scala/searcher/src/test/scala/org/enso/searcher/sql/SuggestionsRepoTest.scala b/lib/scala/searcher/src/test/scala/org/enso/searcher/sql/SuggestionsRepoTest.scala index d4f1854aec..81eb6fc81d 100644 --- a/lib/scala/searcher/src/test/scala/org/enso/searcher/sql/SuggestionsRepoTest.scala +++ b/lib/scala/searcher/src/test/scala/org/enso/searcher/sql/SuggestionsRepoTest.scala @@ -675,6 +675,73 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec { ) } + "update suggestion function documentation" taggedAs Retry in withRepo { + repo => + val newDoc = "My awesome function!" + val action = for { + (v1, Seq(_, _, _, _, _, id1, _)) <- repo.insertAll( + Seq( + suggestion.module, + suggestion.tpe, + suggestion.constructor, + suggestion.method, + suggestion.conversion, + suggestion.function, + suggestion.local + ) + ) + (v2, id2) <- repo.update( + suggestion.function, + None, + None, + None, + Some(Some(newDoc)), + None, + None + ) + s <- repo.select(id1.get) + } yield (v1, id1, v2, id2, s) + val (v1, id1, v2, id2, s) = Await.result(action, Timeout) + v1 should not equal v2 + id1 shouldEqual id2 + s shouldEqual Some( + suggestion.function.copy(documentation = Some(newDoc)) + ) + } + + "update suggestion local documentation" taggedAs Retry in withRepo { repo => + val newDoc = "Some stuff there" + val action = for { + (v1, Seq(_, _, _, _, _, _, id1)) <- repo.insertAll( + Seq( + suggestion.module, + suggestion.tpe, + suggestion.constructor, + suggestion.method, + suggestion.conversion, + suggestion.function, + suggestion.local + ) + ) + (v2, id2) <- repo.update( + suggestion.local, + None, + None, + None, + Some(Some(newDoc)), + None, + None + ) + s <- repo.select(id1.get) + } yield (v1, id1, v2, id2, s) + val (v1, id1, v2, id2, s) = Await.result(action, Timeout) + v1 should not equal v2 + id1 shouldEqual id2 + s shouldEqual Some( + suggestion.local.copy(documentation = Some(newDoc)) + ) + } + "update suggestion removing documentation" taggedAs Retry in withRepo { repo => val action = for { @@ -1898,8 +1965,9 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec { Suggestion.Argument("x", "Number", false, true, Some("0")) ), returnType = "local.Test.Main.MyType", - scope = - Suggestion.Scope(Suggestion.Position(1, 5), Suggestion.Position(6, 0)) + scope = Suggestion + .Scope(Suggestion.Position(1, 5), Suggestion.Position(6, 0)), + documentation = Some("My function bar.") ) val local: Suggestion.Local = @@ -1911,7 +1979,8 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec { scope = Suggestion.Scope( Suggestion.Position(3, 4), Suggestion.Position(6, 0) - ) + ), + documentation = Some("Some bazz") ) } }