Add Reexport Field to Suggestions (#1793)

Add the reexport field to suggestions
This commit is contained in:
Dmitry Bushev 2021-07-20 19:10:53 +03:00 committed by GitHub
parent f55d66cb2c
commit 980ba8cb65
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 1941 additions and 340 deletions

View File

@ -428,7 +428,7 @@ val scalatestVersion = "3.3.0-SNAP2"
val shapelessVersion = "2.4.0-M1"
val slf4jVersion = "1.7.30"
val slickVersion = "3.3.2"
val sqliteVersion = "3.31.1"
val sqliteVersion = "3.36.0.1"
val tikaVersion = "1.24.1"
val typesafeConfigVersion = "1.4.1"

View File

@ -36,11 +36,6 @@ The license file can be found at `licenses/APACHE2.0`.
Copyright notices related to this dependency can be found in the directory `com.fasterxml.jackson.core.jackson-annotations-2.11.1`.
'sqlite-jdbc', licensed under the The Apache Software License, Version 2.0, is distributed with the engine.
The license information can be found along with the copyright notices.
Copyright notices related to this dependency can be found in the directory `org.xerial.sqlite-jdbc-3.31.1`.
'shapeless_2.13', licensed under the Apache 2, is distributed with the engine.
The license file can be found at `licenses/APACHE2.0`.
Copyright notices related to this dependency can be found in the directory `com.chuusai.shapeless_2.13-2.3.3`.
@ -116,6 +111,11 @@ The license file can be found at `licenses/APACHE2.0`.
Copyright notices related to this dependency can be found in the directory `org.yaml.snakeyaml-1.26`.
'sqlite-jdbc', licensed under the The Apache Software License, Version 2.0, is distributed with the engine.
The license information can be found along with the copyright notices.
Copyright notices related to this dependency can be found in the directory `org.xerial.sqlite-jdbc-3.36.0.1`.
'slick_2.13', licensed under the Two-clause BSD-style license, is distributed with the engine.
The license information can be found along with the copyright notices.
Copyright notices related to this dependency can be found in the directory `com.typesafe.slick.slick_2.13-3.3.2`.

View File

@ -1,19 +1,3 @@
/*
* Copyright 2014 The Error Prone Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Copyright 2016 The Error Prone Authors.
*
@ -30,22 +14,6 @@
* limitations under the License.
*/
/*
* Copyright 2017 The Error Prone Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Copyright 2015 The Error Prone Authors.
*
@ -61,3 +29,35 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Copyright 2014 The Error Prone Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Copyright 2017 The Error Prone Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
/* Copyright (c) 2008 Google Inc.
// Copyright 2003-2010 Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland
// www.source-code.biz, www.inventec.ch/chdh
/* Copyright (c) 2008 Google Inc.

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
/* Copyright (c) 2008 Google Inc.
// Copyright 2003-2010 Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland
// www.source-code.biz, www.inventec.ch/chdh
/* Copyright (c) 2008 Google Inc.

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
/* Copyright (c) 2008 Google Inc.
// Copyright 2003-2010 Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland
// www.source-code.biz, www.inventec.ch/chdh
/* Copyright (c) 2008 Google Inc.

View File

@ -422,6 +422,8 @@ interface SuggestionEntryScope {
// A type of suggestion entries.
type SuggestionEntry =
// A module
| SuggestionEntryModule
// A value constructor
| SuggestionEntryAtom
// A method defined on a type
@ -431,41 +433,104 @@ type SuggestionEntry =
// A local value
| SuggestionEntryLocal;
interface SuggestionEntryAtom {
externalId?: UUID;
name: string;
interface SuggestionEntryModule {
/** The fully qualified module name. */
module: string;
arguments: SuggestionEntryArgument[];
returnType: string;
/** The documentation string. */
documentation?: string;
/** The fully qualified module name re-exporting this module. */
reexport?: string;
}
interface SuggestionEntryAtom {
/** The external id. */
externalId?: UUID;
/** The atom name. */
name: string;
/** The module name where the atom is defined. */
module: string;
/** The list of arguments. */
arguments: SuggestionEntryArgument[];
/** The type of an atom. */
returnType: string;
/** The documentation string. */
documentation?: string;
/** The fully qualified module name re-exporting this module. */
reexport?: string;
documentationHtml?: string;
}
interface SuggestionEntryMethod {
/** The external id. */
externalId?: UUID;
/** The method name. */
name: string;
/** The module name where this method is defined. */
module: string;
/** The list of arguments. */
arguments: SuggestionEntryArgument[];
/** The method self type. */
selfType: string;
/** The return type of this method. */
returnType: string;
/** The documentation string. */
documentation?: string;
/** The fully qualified module name re-exporting this module. */
reexport?: string;
documentationHtml?: string;
}
interface SuggestionEntryFunction {
/** The external id. */
externalId?: UUID;
/** The function name. */
name: string;
/** The module name where this function is defined. */
module: string;
/** The list of arguments. */
arguments: SuggestionEntryArgument[];
/** The function return type. */
returnType: string;
/** The scope where the function is defined. */
scope: SuggestionEntryScope;
}
interface SuggestionEntryLocal {
/** The external id. */
externalId?: UUID;
/** The name of a value. */
name: string;
/** The module where this value is defined. */
module: string;
/** The type of a value. */
returnType: string;
/** The scope where the value is defined. */
scope: SuggestionEntryScope;
}
```
@ -478,7 +543,7 @@ The suggestion entry type that is used as a filter in search requests.
```typescript
// The kind of a suggestion.
type SuggestionEntryType = Atom | Method | Function | Local;
type SuggestionEntryType = Module | Atom | Method | Function | Local;
```
### `SuggestionId`
@ -699,6 +764,11 @@ interface Modify {
* The scope to update.
*/
scope?: FieldUpdate<SuggestionEntryScope>;
/**
* The reexport field to update.
*/
reexport?: FieldUpdate<String>;
}
```

View File

@ -29,6 +29,8 @@ object SearchProtocol {
private object SuggestionType {
val Module = "module"
val Atom = "atom"
val Method = "method"
@ -40,6 +42,10 @@ object SearchProtocol {
implicit val suggestionEncoder: Encoder[Suggestion] =
Encoder.instance[Suggestion] {
case module: Suggestion.Module =>
Encoder[Suggestion.Module]
.apply(module)
.deepMerge(Json.obj(CodecField.Type -> SuggestionType.Module.asJson))
case atom: Suggestion.Atom =>
Encoder[Suggestion.Atom]
.apply(atom)
@ -72,6 +78,9 @@ object SearchProtocol {
implicit val suggestionDecoder: Decoder[Suggestion] =
Decoder.instance { cursor =>
cursor.downField(CodecField.Type).as[String].flatMap {
case SuggestionType.Module =>
Decoder[Suggestion.Module].tryDecode(cursor)
case SuggestionType.Atom =>
Decoder[Suggestion.Atom].tryDecode(cursor)
@ -206,6 +215,7 @@ object SearchProtocol {
* @param documentation the documentation string to update
* @param documentationHtml the HTML documentation to update
* @param scope the scope to update
* @param reexport the module reexporting the suggestion
*/
case class Modify(
id: SuggestionId,
@ -216,7 +226,8 @@ object SearchProtocol {
returnType: Option[FieldUpdate[String]] = None,
documentation: Option[FieldUpdate[String]] = None,
documentationHtml: Option[FieldUpdate[String]] = None,
scope: Option[FieldUpdate[Suggestion.Scope]] = None
scope: Option[FieldUpdate[Suggestion.Scope]] = None,
reexport: Option[FieldUpdate[String]] = None
) extends SuggestionsDatabaseUpdate
implicit val decoder: Decoder[SuggestionsDatabaseUpdate] =
@ -260,6 +271,9 @@ object SearchProtocol {
extends Enum[SuggestionKind]
with CirceEnum[SuggestionKind] {
/** A module suggestion. */
case object Module extends SuggestionKind
/** An atom suggestion. */
case object Atom extends SuggestionKind
@ -281,6 +295,7 @@ object SearchProtocol {
*/
def apply(kind: Suggestion.Kind): SuggestionKind =
kind match {
case Suggestion.Kind.Module => Module
case Suggestion.Kind.Atom => Atom
case Suggestion.Kind.Method => Method
case Suggestion.Kind.Function => Function
@ -294,6 +309,7 @@ object SearchProtocol {
*/
def toSuggestion(kind: SuggestionKind): Suggestion.Kind =
kind match {
case Module => Suggestion.Kind.Module
case Atom => Suggestion.Kind.Atom
case Method => Suggestion.Kind.Method
case Function => Suggestion.Kind.Function

View File

@ -1,6 +1,7 @@
package org.enso.languageserver.search
import java.util.UUID
import java.util.concurrent.Executors
import akka.actor.{Actor, ActorRef, Props, Stash, Status}
import akka.pattern.{ask, pipe}
@ -42,7 +43,8 @@ import org.enso.searcher.{SuggestionsRepo, VersionsRepo}
import org.enso.text.ContentVersion
import org.enso.text.editing.model.Position
import scala.concurrent.Future
import scala.collection.mutable
import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Failure, Success}
/** The handler of search requests.
@ -96,8 +98,7 @@ final class SuggestionsHandler(
with LazyLogging
with UnhandledLogging {
import SuggestionsHandler.ProjectNameUpdated
import context.dispatcher
import SuggestionsHandler._
private val timeout = config.executionContext.requestTimeout
@ -195,7 +196,7 @@ final class SuggestionsHandler(
case SuggestionsHandler.Verified =>
logger.info("Verified.")
context.become(initialized(projectName, graph, Set()))
context.become(initialized(projectName, graph, Set(), State()))
unstashAll()
case Status.Failure(ex) =>
@ -212,23 +213,33 @@ final class SuggestionsHandler(
def initialized(
projectName: String,
graph: TypeGraph,
clients: Set[ClientId]
clients: Set[ClientId],
state: State
): Receive = {
case AcquireCapability(
client,
CapabilityRegistration(ReceivesSuggestionsDatabaseUpdates())
) =>
sender() ! CapabilityAcquired
context.become(initialized(projectName, graph, clients + client.clientId))
context.become(
initialized(projectName, graph, clients + client.clientId, state)
)
case ReleaseCapability(
client,
CapabilityRegistration(ReceivesSuggestionsDatabaseUpdates())
) =>
sender() ! CapabilityReleased
context.become(initialized(projectName, graph, clients - client.clientId))
context.become(
initialized(projectName, graph, clients - client.clientId, state)
)
case msg: Api.SuggestionsDatabaseModuleUpdateNotification
if state.isSuggestionUpdatesRunning =>
state.suggestionUpdatesQueue.enqueue(msg)
case msg: Api.SuggestionsDatabaseModuleUpdateNotification =>
logger.debug("Got module update [{}].", msg.module)
val isVersionChanged =
versionsRepo.getVersion(msg.module).map { digestOpt =>
!digestOpt.map(ContentVersion(_)).contains(msg.version)
@ -241,12 +252,19 @@ final class SuggestionsHandler(
applyUpdatesIfVersionChanged
.onComplete {
case Success(Some(notification)) =>
logger.debug("Complete module update [{}].", msg.module)
if (notification.updates.nonEmpty) {
clients.foreach { clientId =>
sessionRouter ! DeliverToJsonController(clientId, notification)
}
}
self ! SuggestionsHandler.SuggestionUpdatesCompleted
case Success(None) =>
logger.debug(
"Skip module update, version not changed [{}].",
msg.module
)
self ! SuggestionsHandler.SuggestionUpdatesCompleted
case Failure(ex) =>
logger.error(
"Error applying suggestion database updates [{}, {}]. {}",
@ -254,7 +272,16 @@ final class SuggestionsHandler(
msg.version,
ex.getMessage
)
self ! SuggestionsHandler.SuggestionUpdatesCompleted
}
context.become(
initialized(
projectName,
graph,
clients,
state.copy(isSuggestionUpdatesRunning = true)
)
)
case Api.ExpressionUpdates(_, updates) =>
logger.debug(
@ -446,7 +473,21 @@ final class SuggestionsHandler(
case ProjectNameUpdated(name, updates) =>
updates.foreach(sessionRouter ! _)
context.become(initialized(name, graph, clients))
context.become(initialized(name, graph, clients, state))
case SuggestionUpdatesCompleted =>
if (state.suggestionUpdatesQueue.nonEmpty) {
self ! state.suggestionUpdatesQueue.dequeue()
}
context.become(
initialized(
projectName,
graph,
clients,
state.copy(isSuggestionUpdatesRunning = false)
)
)
}
/** Transition the initialization process.
@ -482,15 +523,17 @@ final class SuggestionsHandler(
msg: Api.SuggestionsDatabaseModuleUpdateNotification
): Future[SuggestionsDatabaseUpdateNotification] =
for {
actionResults <- suggestionsRepo.applyActions(msg.actions)
(version, results) <- suggestionsRepo.applyTree(msg.updates)
_ <- versionsRepo.setVersion(msg.module, msg.version.toDigest)
actionResults <- suggestionsRepo.applyActions(msg.actions)
treeResults <- suggestionsRepo.applyTree(msg.updates)
exportResults <- suggestionsRepo.applyExports(msg.exports)
version <- suggestionsRepo.currentVersion
_ <- versionsRepo.setVersion(msg.module, msg.version.toDigest)
} yield {
val actionUpdates = actionResults.flatMap {
case QueryResult(ids, Api.SuggestionsDatabaseAction.Clean(_)) =>
ids.map(SuggestionsDatabaseUpdate.Remove)
}
val treeUpdates = results.flatMap {
val treeUpdates = treeResults.flatMap {
case QueryResult(ids, Api.SuggestionUpdate(suggestion, action)) =>
val verb = action.getClass.getSimpleName
action match {
@ -517,9 +560,28 @@ final class SuggestionsHandler(
}
}
}
val exportUpdates = exportResults.flatMap { queryResult =>
val update = queryResult.value
update.action match {
case Api.ExportsAction.Add() =>
queryResult.ids.map { id =>
SuggestionsDatabaseUpdate.Modify(
id = id,
reexport = Some(fieldUpdate(update.exports.module))
)
}
case Api.ExportsAction.Remove() =>
queryResult.ids.map { id =>
SuggestionsDatabaseUpdate.Modify(
id = id,
reexport = Some(fieldRemove)
)
}
}
}
SuggestionsDatabaseUpdateNotification(
version,
actionUpdates ++ treeUpdates
actionUpdates ++ treeUpdates ++ exportUpdates
)
}
@ -542,6 +604,9 @@ final class SuggestionsHandler(
private def fieldUpdate[A](value: A): FieldUpdate[A] =
FieldUpdate(FieldAction.Set, Some(value))
private def fieldRemove[A]: FieldUpdate[A] =
FieldUpdate[A](FieldAction.Remove, None)
/** Construct [[SuggestionArgumentUpdate]] from the runtime message.
*
* @param action the runtime message
@ -599,6 +664,9 @@ final class SuggestionsHandler(
object SuggestionsHandler {
implicit private val dispatcher: ExecutionContext =
ExecutionContext.fromExecutorService(Executors.newSingleThreadExecutor())
/** The notification about the project name update.
*
* @param projectName the new project name
@ -622,6 +690,9 @@ object SuggestionsHandler {
/** The notification that the suggestions database has been verified. */
case object Verified
/** The notification that the suggestion updates are processed. */
case object SuggestionUpdatesCompleted
/** The initialization state of the handler.
*
* @param project the project name
@ -647,6 +718,18 @@ object SuggestionsHandler {
} yield (name, graph)
}
/** The suggestion updates state.
*
* @param suggestionUpdatesQueue the queue containing update messages
* @param isSuggestionUpdatesRunning a flag for a running update action
*/
case class State(
suggestionUpdatesQueue: mutable.Queue[
Api.SuggestionsDatabaseModuleUpdateNotification
] = mutable.Queue.empty,
isSuggestionUpdatesRunning: Boolean = false
)
/** Creates a configuration object used to create a [[SuggestionsHandler]].
*
* @param config the server configuration

View File

@ -7,6 +7,12 @@ import org.enso.polyglot.Suggestion
/** Suggestion instances used in tests. */
object Suggestions {
val module: Suggestion.Module = Suggestion.Module(
module = "Test.Main",
documentation = None,
documentationHtml = None
)
val atom: Suggestion.Atom = Suggestion.Atom(
externalId = None,
module = "Test.Main",
@ -95,6 +101,7 @@ object Suggestions {
)
val all = Seq(
module,
atom,
method,
function,

View File

@ -1,5 +1,8 @@
package org.enso.languageserver.search
import java.nio.file.Files
import java.util.UUID
import akka.actor.{ActorRef, ActorSystem}
import akka.testkit.{ImplicitSender, TestKit, TestProbe}
import org.apache.commons.io.FileUtils
@ -14,7 +17,7 @@ import org.enso.languageserver.refactoring.ProjectNameChangedEvent
import org.enso.languageserver.search.SearchProtocol.SuggestionDatabaseEntry
import org.enso.languageserver.session.JsonSession
import org.enso.languageserver.session.SessionRouter.DeliverToJsonController
import org.enso.polyglot.Suggestion
import org.enso.polyglot.{ExportedSymbol, ModuleExports, Suggestion}
import org.enso.polyglot.data.{Tree, TypeGraph}
import org.enso.polyglot.runtime.Runtime.Api
import org.enso.searcher.sql.{SqlDatabase, SqlSuggestionsRepo, SqlVersionsRepo}
@ -26,8 +29,7 @@ import org.scalatest.BeforeAndAfterAll
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike
import java.nio.file.Files
import java.util.UUID
import scala.collection.immutable.ListSet
import scala.concurrent.duration._
import scala.concurrent.{Await, Future}
import scala.util.{Failure, Success}
@ -134,6 +136,7 @@ class SuggestionsHandlerSpec
"Foo.Main",
contentsVersion(""),
Vector(),
Vector(),
Tree.Root(Suggestions.all.toVector.map { suggestion =>
Tree.Node(
Api.SuggestionUpdate(suggestion, Api.SuggestionAction.Add()),
@ -179,6 +182,7 @@ class SuggestionsHandlerSpec
"Foo.Main",
contentsVersion(""),
Vector(),
Vector(),
Tree.Root(
Suggestions.all.toVector
.map { suggestion =>
@ -235,6 +239,13 @@ class SuggestionsHandlerSpec
val tree1 = Tree.Root(
Vector(
Tree.Node(
Api.SuggestionUpdate(
Suggestions.module,
Api.SuggestionAction.Add()
),
Vector()
),
Tree.Node(
Api.SuggestionUpdate(
Suggestions.atom,
@ -273,6 +284,7 @@ class SuggestionsHandlerSpec
"Foo.Main",
contentsVersion(""),
Vector(),
Vector(),
tree1
)
@ -347,12 +359,13 @@ class SuggestionsHandlerSpec
"Foo.Main",
contentsVersion("1"),
Vector(),
Vector(),
tree2
)
val updates2 = Seq(
SearchProtocol.SuggestionsDatabaseUpdate.Modify(
1L,
2L,
arguments = Some(
Seq(
SearchProtocol.SuggestionArgumentUpdate
@ -367,7 +380,7 @@ class SuggestionsHandlerSpec
)
),
SearchProtocol.SuggestionsDatabaseUpdate.Modify(
3L,
4L,
scope = Some(
SearchProtocol.FieldUpdate(
SearchProtocol.FieldAction.Set,
@ -375,8 +388,8 @@ class SuggestionsHandlerSpec
)
)
),
SearchProtocol.SuggestionsDatabaseUpdate.Remove(4L),
SearchProtocol.SuggestionsDatabaseUpdate.Add(5L, Suggestions.local)
SearchProtocol.SuggestionsDatabaseUpdate.Remove(5L),
SearchProtocol.SuggestionsDatabaseUpdate.Add(6L, Suggestions.local)
)
router.expectMsg(
DeliverToJsonController(
@ -401,7 +414,7 @@ class SuggestionsHandlerSpec
expectMsg(CapabilityAcquired)
val moduleName = "Test.Foo"
val moduleAtom = Suggestion.Atom(
val fooAtom = Suggestion.Atom(
externalId = None,
module = moduleName,
name = "Foo",
@ -410,13 +423,17 @@ class SuggestionsHandlerSpec
documentation = None,
documentationHtml = None
)
val fooAtom = moduleAtom.copy(returnType = "Test.Foo.Foo")
val module = Suggestion.Module(
module = moduleName,
documentation = None,
documentationHtml = None
)
val tree = Tree.Root(
Vector(
Tree.Node(
Api.SuggestionUpdate(
moduleAtom,
module,
Api.SuggestionAction.Add()
),
Vector()
@ -436,6 +453,7 @@ class SuggestionsHandlerSpec
"Foo.Main",
contentsVersion(""),
Vector(),
Vector(),
tree
)
@ -514,6 +532,7 @@ class SuggestionsHandlerSpec
"Foo.Main",
contentsVersion(""),
Vector(),
Vector(),
tree1
)
@ -538,6 +557,7 @@ class SuggestionsHandlerSpec
"Foo.Main",
contentsVersion("1"),
Vector(Api.SuggestionsDatabaseAction.Clean(Suggestions.atom.module)),
Vector(),
Tree.Root(Vector())
)
@ -548,7 +568,7 @@ class SuggestionsHandlerSpec
DeliverToJsonController(
clientId,
SearchProtocol.SuggestionsDatabaseUpdateNotification(
updates1.size + 1L,
updates1.size.toLong,
updates2
)
)
@ -559,6 +579,175 @@ class SuggestionsHandlerSpec
all shouldEqual Seq()
}
"apply export updates" taggedAs Retry in withDb {
(_, _, router, _, handler) =>
val clientId = UUID.randomUUID()
// acquire capability
handler ! AcquireCapability(
newJsonSession(clientId),
CapabilityRegistration(ReceivesSuggestionsDatabaseUpdates())
)
expectMsg(CapabilityAcquired)
val tree1 = Tree.Root(
Vector(
Tree.Node(
Api.SuggestionUpdate(
Suggestions.module,
Api.SuggestionAction.Add()
),
Vector()
),
Tree.Node(
Api.SuggestionUpdate(
Suggestions.atom,
Api.SuggestionAction.Add()
),
Vector()
),
Tree.Node(
Api.SuggestionUpdate(
Suggestions.method,
Api.SuggestionAction.Add()
),
Vector(
Tree.Node(
Api.SuggestionUpdate(
Suggestions.function,
Api.SuggestionAction.Add()
),
Vector(
Tree.Node(
Api.SuggestionUpdate(
Suggestions.local,
Api.SuggestionAction.Add()
),
Vector()
)
)
)
)
)
)
)
// add tree
handler ! Api.SuggestionsDatabaseModuleUpdateNotification(
"Foo.Main",
contentsVersion(""),
Vector(),
Vector(),
tree1
)
val updates1 = tree1.toVector.zipWithIndex.map { case (update, ix) =>
SearchProtocol.SuggestionsDatabaseUpdate.Add(
ix + 1L,
update.suggestion
)
}
router.expectMsg(
DeliverToJsonController(
clientId,
SearchProtocol.SuggestionsDatabaseUpdateNotification(
updates1.size.toLong,
updates1
)
)
)
val exportUpdateAdd =
Api.ExportsUpdate(
ModuleExports(
"Foo.Bar",
ListSet(
ExportedSymbol.Module(
Suggestions.module.module
),
ExportedSymbol.Atom(
Suggestions.atom.module,
Suggestions.atom.name
),
ExportedSymbol.Method(
Suggestions.method.module,
Suggestions.method.name
)
)
),
Api.ExportsAction.Add()
)
// apply updates1
handler ! Api.SuggestionsDatabaseModuleUpdateNotification(
"Foo.Main",
contentsVersion("1"),
Vector(),
Vector(exportUpdateAdd),
Tree.Root(Vector())
)
val updates2 = Seq(1L, 2L, 3L).map { id =>
SearchProtocol.SuggestionsDatabaseUpdate.Modify(
id,
reexport = Some(fieldUpdate(exportUpdateAdd.exports.module))
)
}
router.expectMsg(
DeliverToJsonController(
clientId,
SearchProtocol.SuggestionsDatabaseUpdateNotification(
updates1.size.toLong + 1,
updates2
)
)
)
val exportUpdateRemove =
Api.ExportsUpdate(
ModuleExports(
"Foo.Bar",
ListSet(
ExportedSymbol.Module(
Suggestions.module.module
),
ExportedSymbol.Atom(
Suggestions.atom.module,
Suggestions.atom.name
),
ExportedSymbol.Method(
Suggestions.method.module,
Suggestions.method.name
)
)
),
Api.ExportsAction.Remove()
)
// apply updates2
handler ! Api.SuggestionsDatabaseModuleUpdateNotification(
"Foo.Main",
contentsVersion("2"),
Vector(),
Vector(exportUpdateRemove),
Tree.Root(Vector())
)
val updates3 = Seq(1L, 2L, 3L).map { id =>
SearchProtocol.SuggestionsDatabaseUpdate.Modify(
id,
reexport = Some(fieldRemove)
)
}
router.expectMsg(
DeliverToJsonController(
clientId,
SearchProtocol.SuggestionsDatabaseUpdateNotification(
updates1.size.toLong + 2,
updates3
)
)
)
}
"get initial suggestions database version" taggedAs Retry in withDb {
(_, _, _, _, handler) =>
handler ! SearchProtocol.GetSuggestionsDatabaseVersion
@ -706,13 +895,14 @@ class SuggestionsHandlerSpec
expectMsg(
SearchProtocol.CompletionResult(
7L,
Suggestions.all.length.toLong,
Seq(
inserted(0).get,
inserted(6).get,
inserted(4).get,
inserted(1).get,
inserted(7).get,
inserted(5).get,
inserted(1).get
inserted(6).get,
inserted(2).get
)
)
)
@ -720,7 +910,7 @@ class SuggestionsHandlerSpec
"search entries by self type" taggedAs Retry in withDb {
(config, repo, _, _, handler) =>
val (_, Seq(_, methodId, _, _, methodOnAnyId, _, _)) =
val (_, Seq(_, _, methodId, _, _, methodOnAnyId, _, _)) =
Await.result(repo.insertAll(Suggestions.all), Timeout)
handler ! SearchProtocol.Completion(
file = mkModulePath(config, "Main.enso"),
@ -732,7 +922,7 @@ class SuggestionsHandlerSpec
expectMsg(
SearchProtocol.CompletionResult(
7L,
Suggestions.all.length.toLong,
Seq(methodId, methodOnAnyId).flatten
)
)
@ -740,7 +930,10 @@ class SuggestionsHandlerSpec
"search entries based on supertypes of self" taggedAs Retry in withDb {
(config, repo, _, _, handler) =>
val (_, Seq(_, _, _, _, anyMethodId, numberMethodId, integerMethodId)) =
val (
_,
Seq(_, _, _, _, _, anyMethodId, numberMethodId, integerMethodId)
) =
Await.result(repo.insertAll(Suggestions.all), Timeout)
handler ! SearchProtocol.Completion(
@ -753,7 +946,7 @@ class SuggestionsHandlerSpec
expectMsg(
SearchProtocol.CompletionResult(
7L,
Suggestions.all.length.toLong,
Seq(integerMethodId, numberMethodId, anyMethodId).flatten
)
)
@ -761,7 +954,7 @@ class SuggestionsHandlerSpec
"search entries for any" taggedAs Retry in withDb {
(config, repo, _, _, handler) =>
val (_, Seq(_, _, _, _, anyMethodId, _, _)) =
val (_, Seq(_, _, _, _, _, anyMethodId, _, _)) =
Await.result(repo.insertAll(Suggestions.all), Timeout)
handler ! SearchProtocol.Completion(
@ -772,12 +965,17 @@ class SuggestionsHandlerSpec
tags = None
)
expectMsg(SearchProtocol.CompletionResult(7L, Seq(anyMethodId).flatten))
expectMsg(
SearchProtocol.CompletionResult(
Suggestions.all.length.toLong,
Seq(anyMethodId).flatten
)
)
}
"search entries by return type" taggedAs Retry in withDb {
(config, repo, _, _, handler) =>
val (_, Seq(_, _, functionId, _, _, _, _)) =
val (_, Seq(_, _, _, functionId, _, _, _, _)) =
Await.result(repo.insertAll(Suggestions.all), Timeout)
handler ! SearchProtocol.Completion(
file = mkModulePath(config, "Main.enso"),
@ -787,12 +985,17 @@ class SuggestionsHandlerSpec
tags = None
)
expectMsg(SearchProtocol.CompletionResult(7L, Seq(functionId).flatten))
expectMsg(
SearchProtocol.CompletionResult(
Suggestions.all.length.toLong,
Seq(functionId).flatten
)
)
}
"search entries by tags" taggedAs Retry in withDb {
(config, repo, _, _, handler) =>
val (_, Seq(_, _, _, localId, _, _, _)) =
val (_, Seq(_, _, _, _, localId, _, _, _)) =
Await.result(repo.insertAll(Suggestions.all), Timeout)
handler ! SearchProtocol.Completion(
file = mkModulePath(config, "Main.enso"),
@ -802,13 +1005,21 @@ class SuggestionsHandlerSpec
tags = Some(Seq(SearchProtocol.SuggestionKind.Local))
)
expectMsg(SearchProtocol.CompletionResult(7L, Seq(localId).flatten))
expectMsg(
SearchProtocol.CompletionResult(
Suggestions.all.length.toLong,
Seq(localId).flatten
)
)
}
}
private def fieldUpdate(value: String): SearchProtocol.FieldUpdate[String] =
SearchProtocol.FieldUpdate(SearchProtocol.FieldAction.Set, Some(value))
private def fieldRemove[A]: SearchProtocol.FieldUpdate[A] =
SearchProtocol.FieldUpdate(SearchProtocol.FieldAction.Remove, None)
def newSuggestionsHandler(
config: Config,
sessionRouter: TestProbe,

View File

@ -3,10 +3,13 @@ package org.enso.languageserver.websocket.json
import io.circe.literal._
import org.enso.languageserver.search.Suggestions
import org.enso.languageserver.websocket.json.{SearchJsonMessages => json}
import org.enso.polyglot.{ExportedSymbol, ModuleExports}
import org.enso.polyglot.data.Tree
import org.enso.polyglot.runtime.Runtime.Api
import org.enso.testkit.FlakySpec
import scala.collection.immutable.ListSet
class SuggestionsHandlerEventsTest extends BaseServerTest with FlakySpec {
"SuggestionsHandlerEvents" must {
@ -23,6 +26,7 @@ class SuggestionsHandlerEventsTest extends BaseServerTest with FlakySpec {
"Foo.Main",
versionCalculator.evalVersion("1"),
Vector(),
Vector(),
Tree.Root(
Vector(
Tree.Node(
@ -72,6 +76,7 @@ class SuggestionsHandlerEventsTest extends BaseServerTest with FlakySpec {
"Foo.Main",
versionCalculator.evalVersion("2"),
Vector(),
Vector(),
Tree.Root(
Vector(
Tree.Node(
@ -140,6 +145,7 @@ class SuggestionsHandlerEventsTest extends BaseServerTest with FlakySpec {
"Foo.Main",
versionCalculator.evalVersion("3"),
Vector(),
Vector(),
Tree.Root(
Vector(
Tree.Node(
@ -230,6 +236,7 @@ class SuggestionsHandlerEventsTest extends BaseServerTest with FlakySpec {
"Foo.Main",
versionCalculator.evalVersion("4"),
Vector(),
Vector(),
Tree.Root(
Vector(
Tree.Node(
@ -300,30 +307,28 @@ class SuggestionsHandlerEventsTest extends BaseServerTest with FlakySpec {
""")
// get suggestions database
client.send(json.getSuggestionsDatabase(0))
client.send(json.getSuggestionsDatabase(3))
client.expectJson(json"""
{ "jsonrpc" : "2.0",
"id" : 0,
"id" : 3,
"result" : {
"entries" : [
{
"id" : 4,
{
"id" : 1,
"suggestion" : {
"type" : "local",
"externalId" : "dc077227-d9b6-4620-9b51-792c2a69419d",
"type" : "atom",
"module" : "Test.Main",
"name" : "x",
"returnType" : "Number",
"scope" : {
"start" : {
"line" : 21,
"character" : 0
},
"end" : {
"line" : 89,
"character" : 0
"name" : "MyType",
"arguments" : [
{
"name" : "a",
"reprType" : "Any",
"isSuspended" : false,
"hasDefault" : false,
"defaultValue" : null
}
}
],
"returnType" : "MyAtom"
}
},
{
@ -399,21 +404,23 @@ class SuggestionsHandlerEventsTest extends BaseServerTest with FlakySpec {
}
},
{
"id" : 1,
"id" : 4,
"suggestion" : {
"type" : "atom",
"type" : "local",
"externalId" : ${Suggestions.local.externalId.get},
"module" : "Test.Main",
"name" : "MyType",
"arguments" : [
{
"name" : "a",
"reprType" : "Any",
"isSuspended" : false,
"hasDefault" : false,
"defaultValue" : null
"name" : "x",
"returnType" : "Number",
"scope" : {
"start" : {
"line" : 21,
"character" : 0
},
"end" : {
"line" : 89,
"character" : 0
}
],
"returnType" : "MyAtom"
}
}
}
],
@ -428,6 +435,7 @@ class SuggestionsHandlerEventsTest extends BaseServerTest with FlakySpec {
"Foo.Main",
versionCalculator.evalVersion("5"),
Vector(),
Vector(),
Tree.Root(
Vector(
Tree.Node(
@ -540,12 +548,54 @@ class SuggestionsHandlerEventsTest extends BaseServerTest with FlakySpec {
}
""")
// remove items
// update exports
system.eventStream.publish(
Api.SuggestionsDatabaseModuleUpdateNotification(
"Foo.Main",
versionCalculator.evalVersion("6"),
Vector(),
Vector(
Api.ExportsUpdate(
ModuleExports(
"Foo.Bar",
ListSet(
ExportedSymbol
.Atom(Suggestions.atom.module, Suggestions.atom.name)
)
),
Api.ExportsAction.Add()
)
),
Tree.Root(Vector())
)
)
client.expectJson(json"""
{
"jsonrpc" : "2.0",
"method" : "search/suggestionsDatabaseUpdates",
"params" : {
"updates" : [
{
"type" : "Modify",
"id" : 1,
"reexport" : {
"tag" : "Set",
"value" : "Foo.Bar"
}
}
],
"currentVersion" : 8
}
}
""")
// remove items
system.eventStream.publish(
Api.SuggestionsDatabaseModuleUpdateNotification(
"Foo.Main",
versionCalculator.evalVersion("7"),
Vector(Api.SuggestionsDatabaseAction.Clean(Suggestions.atom.module)),
Vector(),
Tree.Root(Vector())
)
)

View File

@ -0,0 +1,65 @@
package org.enso.polyglot
import com.fasterxml.jackson.annotation.{JsonSubTypes, JsonTypeInfo}
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
@JsonSubTypes(
Array(
new JsonSubTypes.Type(
value = classOf[ExportedSymbol.Module],
name = "exportedModule"
),
new JsonSubTypes.Type(
value = classOf[ExportedSymbol.Atom],
name = "exportedAtom"
),
new JsonSubTypes.Type(
value = classOf[ExportedSymbol.Method],
name = "exportedMethod"
)
)
)
sealed trait ExportedSymbol {
def module: String
def name: String
def kind: Suggestion.Kind
}
object ExportedSymbol {
/** The module symbol.
*
* @param module the module name
*/
case class Module(module: String) extends ExportedSymbol {
override def name: String =
module
override def kind: Suggestion.Kind =
Suggestion.Kind.Module
}
/** The atom symbol.
*
* @param module the module defining this atom
* @param name the atom name
*/
case class Atom(module: String, name: String) extends ExportedSymbol {
override def kind: Suggestion.Kind =
Suggestion.Kind.Atom
}
/** The method symbol.
*
* @param module the module defining this method
* @param name the method name
*/
case class Method(module: String, name: String) extends ExportedSymbol {
override def kind: Suggestion.Kind =
Suggestion.Kind.Method
}
}

View File

@ -0,0 +1,8 @@
package org.enso.polyglot
/** The module exporting a set of symbols.
*
* @param module the module name
* @param symbols the set of exported symbols
*/
case class ModuleExports(module: String, symbols: Set[ExportedSymbol])

View File

@ -9,6 +9,10 @@ import org.enso.logger.masking.ToLogString
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
@JsonSubTypes(
Array(
new JsonSubTypes.Type(
value = classOf[Suggestion.Module],
name = "suggestionModule"
),
new JsonSubTypes.Type(
value = classOf[Suggestion.Atom],
name = "suggestionAtom"
@ -45,12 +49,16 @@ object Suggestion {
def apply(suggestion: Suggestion): Kind =
suggestion match {
case _: Module => Module
case _: Atom => Atom
case _: Method => Method
case _: Function => Function
case _: Local => Local
}
/** The module suggestion. */
case object Module extends Kind
/** The atom suggestion. */
case object Atom extends Kind
@ -69,6 +77,7 @@ object Suggestion {
def apply(suggestion: Suggestion): Option[String] =
suggestion match {
case _: Module => None
case _: Atom => None
case method: Method => Some(method.selfType)
case _: Function => None
@ -117,6 +126,37 @@ object Suggestion {
*/
case class Scope(start: Position, end: Position)
/** A module.
*
* @param module the fully qualified module name
* @param documentation the documentation string
* @param documentationHtml the documentation rendered as HTML
* @param reexport the module re-exporting this module
*/
case class Module(
module: String,
documentation: Option[String],
documentationHtml: Option[String],
reexport: Option[String] = None
) extends Suggestion
with ToLogString {
override def name: String =
module
override def externalId: Option[ExternalId] =
None
override def returnType: String =
module
/** @inheritdoc */
override def toLogString(shouldMask: Boolean): String =
s"Module(module=$module,name=$name,documentation=" +
(if (shouldMask) documentation.map(_ => STUB) else documentation) +
s",reexport=$reexport)"
}
/** A value constructor.
*
* @param externalId the external id
@ -125,6 +165,8 @@ object Suggestion {
* @param arguments the list of arguments
* @param returnType the type of an atom
* @param documentation the documentation string
* @param documentationHtml the documentation rendered as HTML
* @param reexport the module re-exporting this atom
*/
case class Atom(
externalId: Option[ExternalId],
@ -133,7 +175,8 @@ object Suggestion {
arguments: Seq[Argument],
returnType: String,
documentation: Option[String],
documentationHtml: Option[String]
documentationHtml: Option[String],
reexport: Option[String] = None
) extends Suggestion
with ToLogString {
@ -148,7 +191,8 @@ object Suggestion {
s",documentation=" + (if (shouldMask) documentation.map(_ => STUB)
else documentation) +
s",documentationHtml=" + (if (shouldMask) documentationHtml.map(_ => STUB)
else documentationHtml) + ")"
else documentationHtml) +
s",reexport=$reexport)"
}
/** A function defined on a type or a module.
@ -156,10 +200,12 @@ object Suggestion {
* @param externalId the external id
* @param module the module name
* @param name the method name
* @param arguments the function arguments
* @param arguments the list of arguments
* @param selfType the self type of a method
* @param returnType the return type of a method
* @param documentation the documentation string
* @param documentationHtml the documentation rendered as HTML
* @param reexport the module re-exporting this method
*/
case class Method(
externalId: Option[ExternalId],
@ -169,7 +215,8 @@ object Suggestion {
selfType: String,
returnType: String,
documentation: Option[String],
documentationHtml: Option[String]
documentationHtml: Option[String],
reexport: Option[String] = None
) extends Suggestion
with ToLogString {
@ -185,7 +232,7 @@ object Suggestion {
else documentation) +
s",documentationHtml=" + (if (shouldMask) documentationHtml.map(_ => STUB)
else documentationHtml) +
")"
s",reexport=$reexport)"
}
/** A local function definition.

View File

@ -8,7 +8,7 @@ import com.fasterxml.jackson.module.scala.{
ScalaObjectMapper
}
import org.enso.logger.masking.{MaskedPath, MaskedString, ToLogString}
import org.enso.polyglot.Suggestion
import org.enso.polyglot.{ModuleExports, Suggestion}
import org.enso.polyglot.data.{Tree, TypeGraph}
import org.enso.text.ContentVersion
import org.enso.text.editing.model
@ -576,6 +576,7 @@ object Runtime {
* @param documentation the documentation string to update
* @param documentationHtml the HTML documentation to update
* @param scope the scope to update
* @param reexport the reexport field to update
*/
case class Modify(
externalId: Option[Option[Suggestion.ExternalId]] = None,
@ -583,7 +584,8 @@ object Runtime {
returnType: Option[String] = None,
documentation: Option[Option[String]] = None,
documentationHtml: Option[Option[String]] = None,
scope: Option[Suggestion.Scope] = None
scope: Option[Suggestion.Scope] = None,
reexport: Option[Option[String]] = None
) extends SuggestionAction
with ToLogString {
@ -592,6 +594,7 @@ object Runtime {
"Modify(" +
s"externalId=$externalId," +
s"artuments=${arguments.map(_.map(_.toLogString(shouldMask)))}," +
s"returnType=$returnType" +
s"documentation=" +
(if (shouldMask) documentation.map(_.map(_ => STUB))
else documentation) +
@ -599,6 +602,7 @@ object Runtime {
(if (shouldMask) documentationHtml.map(_.map(_ => STUB))
else documentationHtml) +
s",scope=$scope" +
s"reexport=$reexport" +
")"
}
}
@ -623,6 +627,30 @@ object Runtime {
case class Clean(module: String) extends SuggestionsDatabaseAction
}
case class ExportsUpdate(
exports: ModuleExports,
action: ExportsAction
)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
@JsonSubTypes(
Array(
new JsonSubTypes.Type(
value = classOf[ExportsAction.Add],
name = "exportsActionAdd"
),
new JsonSubTypes.Type(
value = classOf[ExportsAction.Remove],
name = "exportsActionRemove"
)
)
)
sealed trait ExportsAction
object ExportsAction {
case class Add() extends ExportsAction
case class Remove() extends ExportsAction
}
/** A suggestion update.
*
* @param suggestion the original suggestion
@ -635,7 +663,7 @@ object Runtime {
/** @inheritdoc */
override def toLogString(shouldMask: Boolean): String =
"SuggestionUpdate(" +
"SuggestionUpdate(suggestion=" +
suggestion.toLogString(shouldMask) +
s",action=${action.toLogString(shouldMask)})"
}
@ -1264,12 +1292,14 @@ object Runtime {
* @param module the module name
* @param version the version of the module
* @param actions the list of actions to apply to the suggestions database
* @param exports the list of re-exported symbols
* @param updates the list of suggestions extracted from module
*/
case class SuggestionsDatabaseModuleUpdateNotification(
module: String,
version: ContentVersion,
actions: Vector[SuggestionsDatabaseAction],
exports: Seq[ExportsUpdate],
updates: Tree[SuggestionUpdate]
) extends ApiNotification
with ToLogString {
@ -1280,6 +1310,7 @@ object Runtime {
s"module=$module," +
s"version=$version," +
s"actions=$actions," +
s"exports=$exports" +
s"updates=${updates.map(_.toLogString(shouldMask))}" +
")"
}

View File

@ -53,6 +53,11 @@ public class ModuleScope implements TruffleObject {
return module;
}
/** @return the set of modules imported by this module. */
public Set<ModuleScope> getImports() {
return imports;
}
/**
* Looks up a constructor in the module scope locally.
*

View File

@ -0,0 +1,36 @@
package org.enso.compiler.context
import org.enso.compiler.core.IR
import org.enso.compiler.data.BindingsMap
import org.enso.compiler.pass.analyse.BindingAnalysis
import org.enso.pkg.QualifiedName
import org.enso.polyglot.{ExportedSymbol, ModuleExports}
final class ExportsBuilder {
/** Build module exports from the given IR.
*
* @param moduleName the module name
* @param ir the module IR
*/
def build(moduleName: QualifiedName, ir: IR): ModuleExports = {
val symbols = getBindings(ir).exportedSymbols.values.flatten
.filter(_.module.getName != moduleName)
.collect {
case BindingsMap.ResolvedMethod(module, method) =>
ExportedSymbol.Method(module.getName.toString, method.name)
case BindingsMap.ResolvedConstructor(module, cons) =>
ExportedSymbol.Atom(module.getName.toString, cons.name)
case BindingsMap.ResolvedModule(module) =>
ExportedSymbol.Module(module.getName.toString)
}
.toSet[ExportedSymbol]
ModuleExports(moduleName.toString, symbols)
}
private def getBindings(ir: IR): BindingsMap =
ir.unsafeGetMetadata(
BindingAnalysis,
"Module without binding analysis in Exports Builder."
)
}

View File

@ -0,0 +1,31 @@
package org.enso.compiler.context
import org.enso.polyglot.ModuleExports
import org.enso.polyglot.runtime.Runtime.Api
object ModuleExportsDiff {
/** Compute difference between the module exports.
*
* @param prev exports before
* @param current exports after
* @return the list of updates
*/
def compute(
prev: ModuleExports,
current: ModuleExports
): Seq[Api.ExportsUpdate] = {
val added = current.symbols.diff(prev.symbols)
val removed = prev.symbols.diff(current.symbols)
val addedUpdate = Option.when(added.nonEmpty) {
Api.ExportsUpdate(current.copy(symbols = added), Api.ExportsAction.Add())
}
val removedUpdate = Option.when(removed.nonEmpty) {
Api.ExportsUpdate(
current.copy(symbols = removed),
Api.ExportsAction.Remove()
)
}
(addedUpdate ++ removedUpdate).toSeq
}
}

View File

@ -127,7 +127,7 @@ final class SuggestionBuilder[A: IndexedSource](val source: A) {
}
val builder: TreeBuilder = Vector.newBuilder
builder += Tree.Node(buildModuleAtom(module), Vector())
builder += Tree.Node(buildModule(module), Vector())
Tree.Root(
go(builder, Scope(ir.children, ir.location))
@ -156,7 +156,8 @@ final class SuggestionBuilder[A: IndexedSource](val source: A) {
selfType = selfType.toString,
returnType = buildReturnType(returnTypeDef),
documentation = doc,
documentationHtml = doc.map(DocParserWrapper.runOnPureDoc)
documentationHtml = doc.map(DocParserWrapper.runOnPureDoc),
reexport = None
)
}
@ -204,15 +205,12 @@ final class SuggestionBuilder[A: IndexedSource](val source: A) {
}
/** Build an atom suggestion representing a module. */
private def buildModuleAtom(module: QualifiedName): Suggestion =
Suggestion.Atom(
externalId = None,
private def buildModule(module: QualifiedName): Suggestion =
Suggestion.Module(
module = module.toString,
name = module.item,
arguments = Seq(),
returnType = module.toString,
documentation = None,
documentationHtml = None
documentationHtml = None,
reexport = None
)
/** Build suggestions for an atom definition. */
@ -240,7 +238,8 @@ final class SuggestionBuilder[A: IndexedSource](val source: A) {
arguments = arguments.map(buildArgument),
returnType = module.createChild(name).toString,
documentation = doc,
documentationHtml = doc.map(DocParserWrapper.runOnPureDoc)
documentationHtml = doc.map(DocParserWrapper.runOnPureDoc),
reexport = None
)
/** Build getter methods from atom arguments. */

View File

@ -21,7 +21,15 @@ object SuggestionDiff {
.filter {
case Api.SuggestionUpdate(
_,
Api.SuggestionAction.Modify(None, None, None, None, None, None)
Api.SuggestionAction.Modify(
None,
None,
None,
None,
None,
None,
None
)
) =>
false
case _ =>
@ -52,6 +60,8 @@ object SuggestionDiff {
Api.SuggestionUpdate(e, Api.SuggestionAction.Remove())
case These.There(e) =>
Api.SuggestionUpdate(e, Api.SuggestionAction.Add())
case These.Both(e1: Suggestion.Module, e2: Suggestion.Module) =>
diffModules(e1, e2)
case These.Both(e1: Suggestion.Atom, e2: Suggestion.Atom) =>
diffAtoms(e1, e2)
case These.Both(e1: Suggestion.Method, e2: Suggestion.Method) =>
@ -130,6 +140,17 @@ object SuggestionDiff {
op
}
private def diffModules(
e1: Suggestion.Module,
e2: Suggestion.Module
): Api.SuggestionUpdate = {
var op = Api.SuggestionAction.Modify()
if (e1.documentation != e2.documentation) {
op = op.copy(documentation = Some(e2.documentation))
}
Api.SuggestionUpdate(e1, op)
}
private def diffAtoms(
e1: Suggestion.Atom,
e2: Suggestion.Atom

View File

@ -1,12 +1,14 @@
package org.enso.interpreter.instrument.job
import java.io.{File, IOException}
import java.io.File
import java.util.logging.Level
import cats.implicits._
import org.enso.compiler.context.{
Changeset,
ExportsBuilder,
ModuleContext,
ModuleExportsDiff,
SuggestionBuilder,
SuggestionDiff
}
@ -21,11 +23,14 @@ import org.enso.interpreter.instrument.execution.{
RuntimeContext
}
import org.enso.interpreter.runtime.Module
import org.enso.polyglot.Suggestion
import org.enso.interpreter.runtime.scope.ModuleScope
import org.enso.pkg.QualifiedName
import org.enso.polyglot.{ModuleExports, Suggestion}
import org.enso.polyglot.data.Tree
import org.enso.polyglot.runtime.Runtime.Api
import org.enso.text.buffer.Rope
import scala.collection.mutable
import scala.jdk.CollectionConverters._
import scala.jdk.OptionConverters._
@ -38,6 +43,8 @@ class EnsureCompiledJob(protected val files: Iterable[File])
import EnsureCompiledJob.CompilationStatus
private val exportsBuilder = new ExportsBuilder
/** @inheritdoc */
override def run(implicit ctx: RuntimeContext): CompilationStatus = {
ctx.locking.acquireWriteCompilationLock()
@ -93,12 +100,32 @@ class EnsureCompiledJob(protected val files: Iterable[File])
compile(module)
val changeset = applyEdits(new File(module.getPath))
compile(module)
.map { module =>
.map { moduleScope =>
val cacheInvalidationCommands =
buildCacheInvalidationCommands(changeset, module.getLiteralSource)
buildCacheInvalidationCommands(
changeset,
moduleScope.getModule.getLiteralSource
)
runInvalidationCommands(cacheInvalidationCommands)
// There are two runtime flags that can disable suggestions for project
// and global modules (libraries). They are used primarily in tests to
// disable the suggestion updates and reduce the number of messages that
// runtime sends.
if (ctx.executionService.getContext.isProjectSuggestionsEnabled) {
analyzeModule(module, changeset)
// When the project module is compiled it can involve compilation of
// global (library) modules, so we need to check if the global
// suggestions are enabled as well.
if (ctx.executionService.getContext.isGlobalSuggestionsEnabled) {
getCompiledModules(moduleScope).foreach(analyzeModuleInScope)
} else {
// When the global suggestions are disabled, we will skip indexing
// of external libraries, but still want to index the modules that
// belongs to the project.
val projectModules = getCompiledModules(moduleScope)
.filter(m => rootName(m.getName) == rootName(module.getName))
projectModules.foreach(analyzeModuleInScope)
}
analyzeModule(moduleScope.getModule, changeset)
}
runCompilationDiagnostics(module)
}
@ -115,6 +142,7 @@ class EnsureCompiledJob(protected val files: Iterable[File])
ctx.executionService.getLogger
.log(Level.FINEST, s"Modules in scope: ${modulesInScope.map(_.getName)}")
modulesInScope
.filter(!_.isIndexed)
.map { module =>
compile(module) match {
case Left(err) =>
@ -127,11 +155,12 @@ class EnsureCompiledJob(protected val files: Iterable[File])
)
)
CompilationStatus.Failure
case Right(module) =>
case Right(moduleScope) =>
if (ctx.executionService.getContext.isGlobalSuggestionsEnabled) {
analyzeModuleInScope(module)
getCompiledModules(moduleScope).foreach(analyzeModuleInScope)
analyzeModuleInScope(moduleScope.getModule)
}
runCompilationDiagnostics(module)
runCompilationDiagnostics(moduleScope.getModule)
}
}
}
@ -139,15 +168,6 @@ class EnsureCompiledJob(protected val files: Iterable[File])
private def analyzeModuleInScope(module: Module)(implicit
ctx: RuntimeContext
): Unit = {
try module.getSource
catch {
case e: IOException =>
ctx.executionService.getLogger.log(
Level.SEVERE,
s"Failed to get module source to analyze ${module.getName}",
e
)
}
if (!module.isIndexed && module.getLiteralSource != null) {
ctx.executionService.getLogger
.log(Level.FINEST, s"Analyzing module in scope ${module.getName}")
@ -155,12 +175,15 @@ class EnsureCompiledJob(protected val files: Iterable[File])
val newSuggestions = SuggestionBuilder(module.getLiteralSource)
.build(moduleName, module.getIr)
.filter(isSuggestionGlobal)
val version = ctx.versioning.evalVersion(module.getLiteralSource.toString)
val version = ctx.versioning.evalVersion(module.getLiteralSource.toString)
val prevExports = ModuleExports(moduleName.toString, Set())
val newExports = exportsBuilder.build(module.getName, module.getIr)
val notification = Api.SuggestionsDatabaseModuleUpdateNotification(
module = moduleName.toString,
version = version,
actions =
Vector(Api.SuggestionsDatabaseAction.Clean(moduleName.toString)),
exports = ModuleExportsDiff.compute(prevExports, newExports),
updates = SuggestionDiff.compute(Tree.empty, newSuggestions)
)
sendModuleUpdate(notification)
@ -184,10 +207,14 @@ class EnsureCompiledJob(protected val files: Iterable[File])
.build(moduleName, module.getIr)
val diff = SuggestionDiff
.compute(prevSuggestions, newSuggestions)
val prevExports = exportsBuilder.build(moduleName, changeset.ir)
val newExports = exportsBuilder.build(moduleName, module.getIr)
val exportsDiff = ModuleExportsDiff.compute(prevExports, newExports)
val notification = Api.SuggestionsDatabaseModuleUpdateNotification(
module = moduleName.toString,
version = version,
actions = Vector(),
exports = exportsDiff,
updates = diff
)
sendModuleUpdate(notification)
@ -197,11 +224,14 @@ class EnsureCompiledJob(protected val files: Iterable[File])
val newSuggestions =
SuggestionBuilder(module.getLiteralSource)
.build(moduleName, module.getIr)
val prevExports = ModuleExports(moduleName.toString, Set())
val newExports = exportsBuilder.build(moduleName, module.getIr)
val notification = Api.SuggestionsDatabaseModuleUpdateNotification(
module = moduleName.toString,
version = version,
actions =
Vector(Api.SuggestionsDatabaseAction.Clean(moduleName.toString)),
exports = ModuleExportsDiff.compute(prevExports, newExports),
updates = SuggestionDiff.compute(Tree.empty, newSuggestions)
)
sendModuleUpdate(notification)
@ -278,16 +308,18 @@ class EnsureCompiledJob(protected val files: Iterable[File])
*/
private def compile(
module: Module
)(implicit ctx: RuntimeContext): Either[Throwable, Module] = {
)(implicit ctx: RuntimeContext): Either[Throwable, ModuleScope] = {
val prevStage = module.getCompilationStage
val compilationResult = Either.catchNonFatal {
module.compileScope(ctx.executionService.getContext).getModule
module.compileScope(ctx.executionService.getContext)
}
if (prevStage != module.getCompilationStage) {
ctx.executionService.getLogger
.log(
Level.FINEST,
s"Compiled ${module.getName} $prevStage->${module.getCompilationStage}"
)
}
ctx.executionService.getLogger
.log(
Level.FINEST,
s"Compiled ${module.getName} $prevStage->${module.getCompilationStage}"
)
compilationResult
}
@ -369,7 +401,11 @@ class EnsureCompiledJob(protected val files: Iterable[File])
private def sendModuleUpdate(
payload: Api.SuggestionsDatabaseModuleUpdateNotification
)(implicit ctx: RuntimeContext): Unit =
if (payload.actions.nonEmpty || !payload.updates.isEmpty) {
if (
payload.actions.nonEmpty ||
payload.exports.nonEmpty ||
!payload.updates.isEmpty
) {
ctx.endpoint.sendToClient(Api.Response(payload))
}
@ -406,6 +442,7 @@ class EnsureCompiledJob(protected val files: Iterable[File])
private def isSuggestionGlobal(suggestion: Suggestion): Boolean =
suggestion match {
case _: Suggestion.Module => true
case _: Suggestion.Atom => true
case _: Suggestion.Method => true
case _: Suggestion.Function => false
@ -441,6 +478,37 @@ class EnsureCompiledJob(protected val files: Iterable[File])
ctx: RuntimeContext
): Iterable[Module] =
ctx.executionService.getContext.getTopScope.getModules.asScala
private def getCompiledModules(moduleScope: ModuleScope): Seq[Module] = {
@scala.annotation.tailrec
def go(
queue: mutable.Queue[ModuleScope],
result: mutable.Set[Module]
): Seq[Module] =
if (queue.isEmpty) result.toSeq.reverse
else {
val scope = queue.dequeue()
val scopeImports = scope.getImports
result.add(scope.getModule)
scopeImports.forEach { scopeImport =>
if (!result.contains(scopeImport.getModule)) {
queue.enqueue(scopeImport)
result.add(scopeImport.getModule)
}
}
go(queue, result)
}
val queue = mutable.Queue.empty[ModuleScope]
moduleScope.getImports.forEach { moduleImport =>
queue.enqueue(moduleImport)
}
go(queue, mutable.LinkedHashSet.empty)
}
private def rootName(name: QualifiedName): String =
name.path.headOption.getOrElse(name.item)
}
object EnsureCompiledJob {

View File

@ -20,15 +20,12 @@ class SuggestionBuilderTest extends CompilerTest {
implicit val passManager: PassManager = new Passes(defaultConfig).passManager
private val Module = QualifiedName(List("Unnamed"), "Test")
private val ModuleAtomNode = Tree.Node(
Suggestion.Atom(
externalId = None,
private val ModuleNode = Tree.Node(
Suggestion.Module(
module = Module.toString,
name = Module.item,
arguments = Seq(),
returnType = Module.toString,
documentation = None,
documentationHtml = None
documentationHtml = None,
reexport = None
),
Vector()
)
@ -47,7 +44,7 @@ class SuggestionBuilderTest extends CompilerTest {
build(code, module) shouldEqual Tree.Root(
Vector(
ModuleAtomNode,
ModuleNode,
Tree.Node(
Suggestion.Method(
externalId = None,
@ -77,7 +74,7 @@ class SuggestionBuilderTest extends CompilerTest {
build(code, module) shouldEqual Tree.Root(
Vector(
ModuleAtomNode,
ModuleNode,
Tree.Node(
Suggestion.Method(
externalId = None,
@ -108,7 +105,7 @@ class SuggestionBuilderTest extends CompilerTest {
build(code, module) shouldEqual Tree.Root(
Vector(
ModuleAtomNode,
ModuleNode,
Tree.Node(
Suggestion.Method(
externalId = None,
@ -139,7 +136,7 @@ class SuggestionBuilderTest extends CompilerTest {
build(code, module) shouldEqual Tree.Root(
Vector(
ModuleAtomNode,
ModuleNode,
Tree.Node(
Suggestion.Method(
externalId = None,
@ -170,7 +167,7 @@ class SuggestionBuilderTest extends CompilerTest {
build(code, module) shouldEqual Tree.Root(
Vector(
ModuleAtomNode,
ModuleNode,
Tree.Node(
Suggestion.Method(
externalId = None,
@ -202,7 +199,7 @@ class SuggestionBuilderTest extends CompilerTest {
build(code, module) shouldEqual Tree.Root(
Vector(
ModuleAtomNode,
ModuleNode,
Tree.Node(
Suggestion.Method(
externalId = None,
@ -241,7 +238,7 @@ class SuggestionBuilderTest extends CompilerTest {
build(code, module) shouldEqual Tree.Root(
Vector(
ModuleAtomNode,
ModuleNode,
Tree.Node(
Suggestion.Method(
externalId = None,
@ -274,7 +271,7 @@ class SuggestionBuilderTest extends CompilerTest {
build(code, module) shouldEqual Tree.Root(
Vector(
ModuleAtomNode,
ModuleNode,
Tree.Node(
Suggestion.Method(
externalId = None,
@ -329,7 +326,7 @@ class SuggestionBuilderTest extends CompilerTest {
build(code, module) shouldEqual Tree.Root(
Vector(
ModuleAtomNode,
ModuleNode,
Tree.Node(
Suggestion.Method(
externalId = None,
@ -363,7 +360,7 @@ class SuggestionBuilderTest extends CompilerTest {
build(code, module) shouldEqual Tree.Root(
Vector(
ModuleAtomNode,
ModuleNode,
Tree.Node(
Suggestion.Atom(
externalId = None,
@ -407,7 +404,7 @@ class SuggestionBuilderTest extends CompilerTest {
"""MyAtom.bar a b = a + b"""
val module = code.preprocessModule
build(code, module) shouldEqual Tree.Root(Vector(ModuleAtomNode))
build(code, module) shouldEqual Tree.Root(Vector(ModuleNode))
}
"build method with associated type signature" in {
@ -424,7 +421,7 @@ class SuggestionBuilderTest extends CompilerTest {
build(code, module) shouldEqual Tree.Root(
Vector(
ModuleAtomNode,
ModuleNode,
Tree.Node(
Suggestion.Atom(
externalId = None,
@ -472,7 +469,7 @@ class SuggestionBuilderTest extends CompilerTest {
build(code, module) shouldEqual Tree.Root(
Vector(
ModuleAtomNode,
ModuleNode,
Tree.Node(
Suggestion.Atom(
externalId = None,
@ -515,7 +512,7 @@ class SuggestionBuilderTest extends CompilerTest {
build(code, module) shouldEqual Tree.Root(
Vector(
ModuleAtomNode,
ModuleNode,
Tree.Node(
Suggestion.Method(
externalId = None,
@ -549,7 +546,7 @@ class SuggestionBuilderTest extends CompilerTest {
build(code, module) shouldEqual Tree.Root(
Vector(
ModuleAtomNode,
ModuleNode,
Tree.Node(
Suggestion.Atom(
externalId = None,
@ -593,7 +590,7 @@ class SuggestionBuilderTest extends CompilerTest {
build(code, module) shouldEqual Tree.Root(
Vector(
ModuleAtomNode,
ModuleNode,
Tree.Node(
Suggestion.Method(
externalId = None,
@ -644,7 +641,7 @@ class SuggestionBuilderTest extends CompilerTest {
build(code, module) shouldEqual Tree.Root(
Vector(
ModuleAtomNode,
ModuleNode,
Tree.Node(
Suggestion.Method(
externalId = None,
@ -708,7 +705,7 @@ class SuggestionBuilderTest extends CompilerTest {
build(code, module) shouldEqual Tree.Root(
Vector(
ModuleAtomNode,
ModuleNode,
Tree.Node(
Suggestion.Method(
externalId = None,
@ -759,7 +756,7 @@ class SuggestionBuilderTest extends CompilerTest {
build(code, module) shouldEqual Tree.Root(
Vector(
ModuleAtomNode,
ModuleNode,
Tree.Node(
Suggestion.Atom(
externalId = None,
@ -820,7 +817,7 @@ class SuggestionBuilderTest extends CompilerTest {
build(code, module) shouldEqual Tree.Root(
Vector(
ModuleAtomNode,
ModuleNode,
Tree.Node(
Suggestion.Method(
externalId = None,
@ -867,7 +864,7 @@ class SuggestionBuilderTest extends CompilerTest {
build(code, module) shouldEqual Tree.Root(
Vector(
ModuleAtomNode,
ModuleNode,
Tree.Node(
Suggestion.Method(
externalId = None,
@ -927,7 +924,7 @@ class SuggestionBuilderTest extends CompilerTest {
build(code, module) shouldEqual Tree.Root(
Vector(
ModuleAtomNode,
ModuleNode,
Tree.Node(
Suggestion.Method(
externalId = None,
@ -975,7 +972,7 @@ class SuggestionBuilderTest extends CompilerTest {
build(code, module) shouldEqual Tree.Root(
Vector(
ModuleAtomNode,
ModuleNode,
Tree.Node(
Suggestion.Atom(
externalId = None,
@ -1029,7 +1026,7 @@ class SuggestionBuilderTest extends CompilerTest {
build(code, module) shouldEqual Tree.Root(
Vector(
ModuleAtomNode,
ModuleNode,
Tree.Node(
Suggestion.Atom(
externalId = None,
@ -1093,7 +1090,7 @@ class SuggestionBuilderTest extends CompilerTest {
build(code, module) shouldEqual Tree.Root(
Vector(
ModuleAtomNode,
ModuleNode,
Tree.Node(
Suggestion.Atom(
externalId = None,
@ -1158,7 +1155,7 @@ class SuggestionBuilderTest extends CompilerTest {
build(code, module) shouldEqual Tree.Root(
Vector(
ModuleAtomNode,
ModuleNode,
Tree.Node(
Suggestion.Atom(
externalId = None,
@ -1220,7 +1217,7 @@ class SuggestionBuilderTest extends CompilerTest {
build(code, module) shouldEqual Tree.Root(
Vector(
ModuleAtomNode,
ModuleNode,
Tree.Node(
Suggestion.Atom(
externalId = None,
@ -1285,7 +1282,7 @@ class SuggestionBuilderTest extends CompilerTest {
build(code, module) shouldEqual Tree.Root(
Vector(
ModuleAtomNode,
ModuleNode,
Tree.Node(
Suggestion.Atom(
externalId = None,
@ -1360,7 +1357,7 @@ class SuggestionBuilderTest extends CompilerTest {
build(code, module) shouldEqual Tree.Root(
Vector(
ModuleAtomNode,
ModuleNode,
Tree.Node(
Suggestion.Atom(
externalId = None,
@ -1454,7 +1451,7 @@ class SuggestionBuilderTest extends CompilerTest {
build(code, module) shouldEqual Tree.Root(
Vector(
ModuleAtomNode,
ModuleNode,
Tree.Node(
Suggestion.Atom(
externalId = None,
@ -1533,7 +1530,7 @@ class SuggestionBuilderTest extends CompilerTest {
build(code, module) shouldEqual Tree.Root(
Vector(
ModuleAtomNode,
ModuleNode,
Tree.Node(
Suggestion.Atom(
externalId = None,
@ -1602,7 +1599,7 @@ class SuggestionBuilderTest extends CompilerTest {
build(code, module) shouldEqual Tree.Root(
Vector(
ModuleAtomNode,
ModuleNode,
Tree.Node(
Suggestion
.Atom(
@ -1682,7 +1679,7 @@ class SuggestionBuilderTest extends CompilerTest {
build(code, module) shouldEqual Tree.Root(
Vector(
ModuleAtomNode,
ModuleNode,
Tree.Node(
Suggestion.Method(
externalId =
@ -1719,7 +1716,7 @@ class SuggestionBuilderTest extends CompilerTest {
build(code, module) shouldEqual Tree.Root(
Vector(
ModuleAtomNode,
ModuleNode,
Tree.Node(
Suggestion.Method(
externalId = None,
@ -1775,7 +1772,7 @@ class SuggestionBuilderTest extends CompilerTest {
build(code, module) shouldEqual Tree.Root(
Vector(
ModuleAtomNode,
ModuleNode,
Tree.Node(
Suggestion.Method(
externalId = None,

View File

@ -198,7 +198,7 @@ class RuntimeStdlibTest
val suggestions = responses.collect {
case Api.Response(
None,
Api.SuggestionsDatabaseModuleUpdateNotification(_, _, as, xs)
Api.SuggestionsDatabaseModuleUpdateNotification(_, _, as, _, xs)
) =>
(xs.nonEmpty || as.nonEmpty) shouldBe true
}
@ -208,7 +208,13 @@ class RuntimeStdlibTest
val stdlibSuggestions = responses.collect {
case Api.Response(
None,
Api.SuggestionsDatabaseModuleUpdateNotification(module, _, as, xs)
Api.SuggestionsDatabaseModuleUpdateNotification(
module,
_,
as,
_,
xs
)
) if module.contains("Vector") =>
(xs.nonEmpty || as.nonEmpty) shouldBe true
xs.toVector.head.suggestion.module shouldEqual "Standard.Base.Data.Vector"
@ -219,7 +225,13 @@ class RuntimeStdlibTest
val builtinsSuggestions = responses.collect {
case Api.Response(
None,
Api.SuggestionsDatabaseModuleUpdateNotification(module, _, as, xs)
Api.SuggestionsDatabaseModuleUpdateNotification(
module,
_,
as,
_,
xs
)
) if module.contains("Builtins") =>
(xs.nonEmpty || as.nonEmpty) shouldBe true
}

View File

@ -5,6 +5,7 @@ import java.nio.ByteBuffer
import java.nio.file.{Files, Paths}
import java.util.UUID
import java.util.concurrent.{LinkedBlockingQueue, TimeUnit}
import org.enso.interpreter.runtime.`type`.Constants
import org.enso.pkg.{Package, PackageManager}
import org.enso.polyglot._
@ -163,15 +164,12 @@ class RuntimeSuggestionUpdatesTest
module = moduleName,
version = version,
actions = Vector(Api.SuggestionsDatabaseAction.Clean(moduleName)),
exports = Vector(),
updates = Tree.Root(
Vector(
Tree.Node(
Api.SuggestionUpdate(
Suggestion.Atom(
None,
moduleName,
"Main",
Seq(),
Suggestion.Module(
moduleName,
None,
None
@ -240,6 +238,7 @@ class RuntimeSuggestionUpdatesTest
|""".stripMargin.linesIterator.mkString("\n")
),
actions = Vector(),
exports = Vector(),
updates = Tree.Root(
Vector(
Tree.Node(
@ -324,6 +323,7 @@ class RuntimeSuggestionUpdatesTest
|""".stripMargin.linesIterator.mkString("\n")
),
actions = Vector(),
exports = Vector(),
updates = Tree.Root(
Vector(
Tree.Node(
@ -428,6 +428,7 @@ class RuntimeSuggestionUpdatesTest
|""".stripMargin.linesIterator.mkString("\n")
),
actions = Vector(),
exports = Vector(),
updates = Tree.Root(
Vector(
Tree.Node(
@ -542,6 +543,7 @@ class RuntimeSuggestionUpdatesTest
|""".stripMargin.linesIterator.mkString("\n")
),
actions = Vector(),
exports = Vector(),
updates = Tree.Root(
Vector(
Tree.Node(
@ -682,6 +684,7 @@ class RuntimeSuggestionUpdatesTest
|""".stripMargin.linesIterator.mkString("\n")
),
actions = Vector(),
exports = Vector(),
updates = Tree.Root(
Vector(
Tree.Node(
@ -789,15 +792,12 @@ class RuntimeSuggestionUpdatesTest
module = moduleName,
version = version,
actions = Vector(Api.SuggestionsDatabaseAction.Clean(moduleName)),
exports = Vector(),
updates = Tree.Root(
Vector(
Tree.Node(
Api.SuggestionUpdate(
Suggestion.Atom(
None,
moduleName,
"Main",
Seq(),
Suggestion.Module(
moduleName,
None,
None
@ -908,4 +908,325 @@ class RuntimeSuggestionUpdatesTest
)
}
it should "index exports" in {
val contextId = UUID.randomUUID()
val requestId = UUID.randomUUID()
val moduleName = "Enso_Test.Test.Main"
val mainCode =
"""from Standard.Builtins import all
|
|import Enso_Test.Test.A
|from Enso_Test.Test.A export all
|
|main = IO.println "Hello World!"
|""".stripMargin.linesIterator.mkString("\n")
val aCode =
"""from Standard.Builtins import all
|
|type MyType
| type MkA a
|
|Integer.fortytwo = 42
|
|hello = "Hello World!"
|""".stripMargin.linesIterator.mkString("\n")
val mainVersion = contentsVersion(mainCode)
val mainFile = context.writeMain(mainCode)
val aVersion = contentsVersion(aCode)
val aFile = context.writeInSrcDir("A", aCode)
// create context
context.send(Api.Request(requestId, Api.CreateContextRequest(contextId)))
context.receive shouldEqual Some(
Api.Response(requestId, Api.CreateContextResponse(contextId))
)
// open files
context.send(
Api.Request(Api.OpenFileNotification(mainFile, mainCode))
)
context.receiveNone shouldEqual None
context.send(
Api.Request(Api.OpenFileNotification(aFile, aCode))
)
context.receiveNone shouldEqual None
// push main
context.send(
Api.Request(
requestId,
Api.PushContextRequest(
contextId,
Api.StackItem.ExplicitCall(
Api.MethodPointer(moduleName, "Enso_Test.Test.Main", "main"),
None,
Vector()
)
)
)
)
context.receive(4) should contain theSameElementsAs Seq(
Api.Response(requestId, Api.PushContextResponse(contextId)),
Api.Response(
Api.SuggestionsDatabaseModuleUpdateNotification(
module = "Enso_Test.Test.A",
version = aVersion,
actions =
Vector(Api.SuggestionsDatabaseAction.Clean("Enso_Test.Test.A")),
exports = Vector(),
updates = Tree.Root(
Vector(
Tree.Node(
Api.SuggestionUpdate(
Suggestion.Module("Enso_Test.Test.A", None, None),
Api.SuggestionAction.Add()
),
Vector()
),
Tree.Node(
Api.SuggestionUpdate(
Suggestion.Atom(
None,
"Enso_Test.Test.A",
"MkA",
List(
Suggestion
.Argument("a", Constants.ANY, false, false, None)
),
"Enso_Test.Test.A.MkA",
None,
None
),
Api.SuggestionAction.Add()
),
Vector()
),
Tree.Node(
Api.SuggestionUpdate(
Suggestion.Method(
None,
"Enso_Test.Test.A",
"a",
List(
Suggestion
.Argument(
"this",
"Enso_Test.Test.A.MkA",
false,
false,
None
)
),
"Enso_Test.Test.A.MkA",
Constants.ANY,
None,
None
),
Api.SuggestionAction.Add()
),
Vector()
),
Tree.Node(
Api.SuggestionUpdate(
Suggestion.Method(
None,
"Enso_Test.Test.A",
"fortytwo",
List(
Suggestion.Argument(
"this",
Constants.INTEGER,
false,
false,
None
)
),
Constants.INTEGER,
Constants.ANY,
None,
None
),
Api.SuggestionAction.Add()
),
Vector()
),
Tree.Node(
Api.SuggestionUpdate(
Suggestion.Method(
None,
"Enso_Test.Test.A",
"hello",
List(
Suggestion.Argument(
"this",
"Enso_Test.Test.A",
false,
false,
None
)
),
"Enso_Test.Test.A",
Constants.ANY,
None,
None
),
Api.SuggestionAction.Add()
),
Vector()
)
)
)
)
),
Api.Response(
Api.SuggestionsDatabaseModuleUpdateNotification(
module = moduleName,
version = mainVersion,
actions = Vector(Api.SuggestionsDatabaseAction.Clean(moduleName)),
exports = Vector(
Api.ExportsUpdate(
ModuleExports(
"Enso_Test.Test.Main",
Set(
ExportedSymbol.Atom("Enso_Test.Test.A", "MkA"),
ExportedSymbol.Method("Enso_Test.Test.A", "hello")
)
),
Api.ExportsAction.Add()
)
),
updates = Tree.Root(
Vector(
Tree.Node(
Api.SuggestionUpdate(
Suggestion.Module(
moduleName,
None,
None
),
Api.SuggestionAction.Add()
),
Vector()
),
Tree.Node(
Api.SuggestionUpdate(
Suggestion.Method(
None,
moduleName,
"main",
List(
Suggestion
.Argument(
"this",
"Enso_Test.Test.Main",
false,
false,
None
)
),
"Enso_Test.Test.Main",
Constants.ANY,
None,
None
),
Api.SuggestionAction.Add()
),
Vector()
)
)
)
)
),
context.executionComplete(contextId)
)
context.consumeOut shouldEqual List("Hello World!")
// Modify the file
context.send(
Api.Request(
Api.EditFileNotification(
mainFile,
Seq(
TextEdit(
model.Range(model.Position(3, 32), model.Position(3, 32)),
" hiding hello"
)
)
)
)
)
context.receive(2) should contain theSameElementsAs Seq(
Api.Response(
Api.SuggestionsDatabaseModuleUpdateNotification(
module = moduleName,
version = contentsVersion(
"""from Standard.Builtins import all
|
|import Enso_Test.Test.A
|from Enso_Test.Test.A export all hiding hello
|
|main = IO.println "Hello World!"
|""".stripMargin.linesIterator.mkString("\n")
),
actions = Vector(),
exports = Vector(
Api.ExportsUpdate(
ModuleExports(
"Enso_Test.Test.Main",
Set(ExportedSymbol.Method("Enso_Test.Test.A", "hello"))
),
Api.ExportsAction.Remove()
)
),
updates = Tree.Root(Vector())
)
),
context.executionComplete(contextId)
)
context.consumeOut shouldEqual List("Hello World!")
// Modify the file
context.send(
Api.Request(
Api.EditFileNotification(
mainFile,
Seq(
TextEdit(
model.Range(model.Position(2, 0), model.Position(5, 0)),
""
)
)
)
)
)
context.receive(2) should contain theSameElementsAs Seq(
Api.Response(
Api.SuggestionsDatabaseModuleUpdateNotification(
module = moduleName,
version = contentsVersion(
"""from Standard.Builtins import all
|
|main = IO.println "Hello World!"
|""".stripMargin.linesIterator.mkString("\n")
),
actions = Vector(),
exports = Vector(
Api.ExportsUpdate(
ModuleExports(
"Enso_Test.Test.Main",
Set(ExportedSymbol.Atom("Enso_Test.Test.A", "MkA"))
),
Api.ExportsAction.Remove()
)
),
updates = Tree.Root(Vector())
)
),
context.executionComplete(contextId)
)
context.consumeOut shouldEqual List("Hello World!")
}
}

View File

@ -22,6 +22,7 @@ object SuggestionRandom {
def nextSuggestion(): Suggestion = {
nextKind() match {
case Suggestion.Kind.Module => nextSuggestionModule()
case Suggestion.Kind.Atom => nextSuggestionAtom()
case Suggestion.Kind.Method => nextSuggestionMethod()
case Suggestion.Kind.Function => nextSuggestionFunction()
@ -29,6 +30,13 @@ object SuggestionRandom {
}
}
def nextSuggestionModule(): Suggestion.Module =
Suggestion.Module(
module = nextString(),
documentation = optional(nextString()),
documentationHtml = optional(nextString())
)
def nextSuggestionAtom(): Suggestion.Atom =
Suggestion.Atom(
externalId = optional(UUID.randomUUID()),
@ -84,11 +92,12 @@ object SuggestionRandom {
)
def nextKind(): Suggestion.Kind =
Random.nextInt(4) match {
case 0 => Suggestion.Kind.Atom
case 1 => Suggestion.Kind.Method
case 2 => Suggestion.Kind.Function
case 3 => Suggestion.Kind.Local
Random.nextInt(5) match {
case 0 => Suggestion.Kind.Module
case 1 => Suggestion.Kind.Atom
case 2 => Suggestion.Kind.Method
case 3 => Suggestion.Kind.Function
case 4 => Suggestion.Kind.Local
case x => throw new NoSuchElementException(s"nextKind: $x")
}

View File

@ -3,6 +3,7 @@ package org.enso.searcher
import org.enso.polyglot.Suggestion
import org.enso.polyglot.data.Tree
import org.enso.polyglot.runtime.Runtime.Api.{
ExportsUpdate,
SuggestionArgumentAction,
SuggestionUpdate,
SuggestionsDatabaseAction
@ -84,7 +85,7 @@ trait SuggestionsRepo[F[_]] {
*/
def applyTree(
tree: Tree[SuggestionUpdate]
): F[(Long, Seq[QueryResult[SuggestionUpdate]])]
): F[Seq[QueryResult[SuggestionUpdate]]]
/** Apply the sequence of actions on the database.
*
@ -95,6 +96,15 @@ trait SuggestionsRepo[F[_]] {
actions: Seq[SuggestionsDatabaseAction]
): F[Seq[QueryResult[SuggestionsDatabaseAction]]]
/** Apply the sequence of export updates on the database.
*
* @param updates the list of export updates
* @return the result of applying the updates
*/
def applyExports(
updates: Seq[ExportsUpdate]
): F[Seq[QueryResult[ExportsUpdate]]]
/** Remove the suggestion.
*
* @param suggestion the suggestion to remove
@ -132,7 +142,8 @@ trait SuggestionsRepo[F[_]] {
returnType: Option[String],
documentation: Option[Option[String]],
documentationHtml: Option[Option[String]],
scope: Option[Suggestion.Scope]
scope: Option[Suggestion.Scope],
reexport: Option[Option[String]]
): F[(Long, Option[Long])]
/** Update a list of suggestions by external id.

View File

@ -5,4 +5,4 @@ package org.enso.searcher.data
* @param ids the list of ids affected by the query
* @param value the result value
*/
case class QueryResult[A](ids: Seq[Long], value: A)
case class QueryResult[A](ids: Iterable[Long], value: A)

View File

@ -2,9 +2,11 @@ package org.enso.searcher.sql
import java.util.UUID
import org.enso.polyglot.Suggestion
import org.enso.polyglot.{ExportedSymbol, Suggestion}
import org.enso.polyglot.data.Tree
import org.enso.polyglot.runtime.Runtime.Api.{
ExportsAction,
ExportsUpdate,
SuggestionAction,
SuggestionArgumentAction,
SuggestionUpdate,
@ -39,7 +41,7 @@ final class SqlSuggestionsRepo(val db: SqlDatabase)(implicit
/** Initialize the repo. */
override def init: Future[Unit] =
db.run(initQuery)
db.run(initQuery.transactionally)
/** @inheritdoc */
override def clean: Future[Unit] =
@ -66,7 +68,9 @@ final class SqlSuggestionsRepo(val db: SqlDatabase)(implicit
kinds: Option[Seq[Suggestion.Kind]],
position: Option[Suggestion.Position]
): Future[(Long, Seq[Long])] =
db.run(searchQuery(module, selfType, returnType, kinds, position))
db.run(
searchQuery(module, selfType, returnType, kinds, position)
)
/** @inheritdoc */
override def select(id: Long): Future[Option[Suggestion]] =
@ -80,19 +84,25 @@ final class SqlSuggestionsRepo(val db: SqlDatabase)(implicit
override def insertAll(
suggestions: Seq[Suggestion]
): Future[(Long, Seq[Option[Long]])] =
db.run(insertAllQuery(suggestions))
db.run(insertAllQuery(suggestions).transactionally)
/** @inheritdoc */
override def applyTree(
tree: Tree[SuggestionUpdate]
): Future[(Long, Seq[QueryResult[SuggestionUpdate]])] =
db.run(applyTreeQuery(tree))
): Future[Seq[QueryResult[SuggestionUpdate]]] =
db.run(applyTreeQuery(tree).transactionally)
/** @inheritdoc */
override def applyActions(
actions: Seq[SuggestionsDatabaseAction]
): Future[Seq[QueryResult[SuggestionsDatabaseAction]]] =
db.run(applyActionsQuery(actions))
db.run(applyActionsQuery(actions).transactionally)
/** @inheritdoc */
override def applyExports(
updates: Seq[ExportsUpdate]
): Future[Seq[QueryResult[ExportsUpdate]]] =
db.run(applyExportsQuery(updates).transactionally)
/** @inheritdoc */
override def remove(suggestion: Suggestion): Future[Option[Long]] =
@ -106,7 +116,7 @@ final class SqlSuggestionsRepo(val db: SqlDatabase)(implicit
override def removeAll(
suggestions: Seq[Suggestion]
): Future[(Long, Seq[Option[Long]])] =
db.run(removeAllQuery(suggestions))
db.run(removeAllQuery(suggestions).transactionally)
/** @inheritdoc */
override def update(
@ -116,7 +126,8 @@ final class SqlSuggestionsRepo(val db: SqlDatabase)(implicit
returnType: Option[String],
documentation: Option[Option[String]],
documentationHtml: Option[Option[String]],
scope: Option[Suggestion.Scope]
scope: Option[Suggestion.Scope],
reexport: Option[Option[String]]
): Future[(Long, Option[Long])] =
db.run(
updateQuery(
@ -126,7 +137,8 @@ final class SqlSuggestionsRepo(val db: SqlDatabase)(implicit
returnType,
documentation,
documentationHtml,
scope
scope,
reexport
)
)
@ -134,7 +146,7 @@ final class SqlSuggestionsRepo(val db: SqlDatabase)(implicit
override def updateAll(
expressions: Seq[(Suggestion.ExternalId, String)]
): Future[(Long, Seq[Option[Long]])] =
db.run(updateAllQuery(expressions))
db.run(updateAllQuery(expressions).transactionally)
/** @inheritdoc */
override def renameProject(
@ -149,7 +161,7 @@ final class SqlSuggestionsRepo(val db: SqlDatabase)(implicit
Seq[(Long, Int, String)]
)
] =
db.run(renameProjectQuery(oldName, newName))
db.run(renameProjectQuery(oldName, newName).transactionally)
/** @inheritdoc */
override def currentVersion: Future[Long] =
@ -184,7 +196,7 @@ final class SqlSuggestionsRepo(val db: SqlDatabase)(implicit
* @return the current database size
*/
private[sql] def insertBatch(suggestions: Array[Suggestion]): Future[Int] =
db.run(insertBatchQuery(suggestions))
db.run(insertBatchQuery(suggestions).transactionally)
/** The query to initialize the repo. */
private def initQuery: DBIO[Unit] = {
@ -376,8 +388,8 @@ final class SqlSuggestionsRepo(val db: SqlDatabase)(implicit
*/
private def applyTreeQuery(
tree: Tree[SuggestionUpdate]
): DBIO[(Long, Seq[QueryResult[SuggestionUpdate]])] = {
val query = tree.toVector.map {
): DBIO[Seq[QueryResult[SuggestionUpdate]]] = {
val queries = tree.toVector.map {
case update @ SuggestionUpdate(suggestion, action) =>
val query = action match {
case SuggestionAction.Add() =>
@ -390,7 +402,8 @@ final class SqlSuggestionsRepo(val db: SqlDatabase)(implicit
returnType,
doc,
docHtml,
scope
scope,
reexport
) =>
updateSuggestionQuery(
suggestion,
@ -399,15 +412,13 @@ final class SqlSuggestionsRepo(val db: SqlDatabase)(implicit
returnType,
doc,
docHtml,
scope
scope,
reexport
)
}
query.map(rs => QueryResult(rs.toSeq, update))
}
for {
results <- DBIO.sequence(query)
version <- currentVersionQuery
} yield (version, results)
DBIO.sequence(queries)
}
/** The query to apply the sequence of actions on the database.
@ -418,13 +429,77 @@ final class SqlSuggestionsRepo(val db: SqlDatabase)(implicit
private def applyActionsQuery(
actions: Seq[SuggestionsDatabaseAction]
): DBIO[Seq[QueryResult[SuggestionsDatabaseAction]]] = {
val queries = actions.map {
val removeActions = actions.map {
case act @ SuggestionsDatabaseAction.Clean(module) =>
removeByModuleQuery(Seq(module)).map { case (_, ids) =>
QueryResult[SuggestionsDatabaseAction](ids, act)
}
for {
ids <- removeModulesQuery(Seq(module))
} yield QueryResult[SuggestionsDatabaseAction](ids, act)
}
DBIO.sequence(queries)
DBIO.sequence(removeActions)
}
/** The query that applies the sequence of export updates.
*
* @param updates the list of export updates
* @return the result of applying actions
*/
private def applyExportsQuery(
updates: Seq[ExportsUpdate]
): DBIO[Seq[QueryResult[ExportsUpdate]]] = {
def depth(module: String): Int =
module.count(_ == '.')
def updateSuggestionReexport(module: String, symbol: ExportedSymbol) = {
val moduleDepth = depth(module)
sql"""
update suggestions
set reexport = $module
where module = ${symbol.module}
and name = ${symbol.name}
and kind = ${SuggestionKind(symbol.kind)}
and (
reexport is null or
length(reexport) - length(replace(reexport, '.', '')) > $moduleDepth
)
returning id
""".as[Long]
}
def unsetSuggestionReexport(module: String, symbol: ExportedSymbol) =
sql"""
update suggestions
set reexport = null
where module = ${symbol.module}
and name = ${symbol.name}
and kind = ${SuggestionKind(symbol.kind)}
and reexport = $module
returning id
""".as[Long]
val actions = updates.flatMap { update =>
val symbols = update.exports.symbols.toSeq
update.action match {
case ExportsAction.Add() =>
symbols.map { symbol =>
for {
ids <- updateSuggestionReexport(update.exports.module, symbol)
} yield QueryResult(ids, update)
}
case ExportsAction.Remove() =>
symbols.map { symbol =>
for {
ids <- unsetSuggestionReexport(update.exports.module, symbol)
} yield QueryResult(ids, update)
}
}
}
for {
rs <- DBIO.sequence(actions)
_ <-
if (rs.flatMap(_.ids).nonEmpty) incrementVersionQuery
else DBIO.successful(())
} yield rs
}
/** The query to select the suggestion.
@ -470,15 +545,29 @@ final class SqlSuggestionsRepo(val db: SqlDatabase)(implicit
private def removeByModuleQuery(
modules: Seq[String]
): DBIO[(Long, Seq[Long])] = {
val selectQuery = Suggestions.filter(_.module.inSet(modules))
val deleteQuery = for {
rows <- selectQuery.result
n <- selectQuery.delete
version <- if (n > 0) incrementVersionQuery else currentVersionQuery
} yield version -> rows.flatMap(_.id)
ids <- removeModulesQuery(modules)
version <-
if (ids.nonEmpty) incrementVersionQuery else currentVersionQuery
} yield version -> ids
deleteQuery
}
/** The query to remove the suggestions by module name
*
* @param modules the module names to remove
* @return the list of removed suggestion ids
*/
private def removeModulesQuery(
modules: Seq[String]
): DBIO[Seq[Long]] = {
val selectQuery = Suggestions.filter(_.module.inSet(modules))
for {
rows <- selectQuery.map(_.id).result
_ <- selectQuery.delete
} yield rows
}
/** The query to remove a list of suggestions.
*
* @param suggestions the suggestions to remove
@ -532,7 +621,8 @@ final class SqlSuggestionsRepo(val db: SqlDatabase)(implicit
returnType: Option[String],
documentation: Option[Option[String]],
documentationHtml: Option[Option[String]],
scope: Option[Suggestion.Scope]
scope: Option[Suggestion.Scope],
reexport: Option[Option[String]]
): DBIO[(Long, Option[Long])] =
for {
idOpt <- updateSuggestionQuery(
@ -542,7 +632,8 @@ final class SqlSuggestionsRepo(val db: SqlDatabase)(implicit
returnType,
documentation,
documentationHtml,
scope
scope,
reexport
)
version <- currentVersionQuery
} yield (version, idOpt)
@ -564,7 +655,8 @@ final class SqlSuggestionsRepo(val db: SqlDatabase)(implicit
returnType: Option[String],
documentation: Option[Option[String]],
documentationHtml: Option[Option[String]],
scope: Option[Suggestion.Scope]
scope: Option[Suggestion.Scope],
reexport: Option[Option[String]]
): DBIO[Option[Long]] = {
val (raw, _) = toSuggestionRow(suggestion)
val query = selectSuggestionQuery(raw)
@ -619,7 +711,12 @@ final class SqlSuggestionsRepo(val db: SqlDatabase)(implicit
} yield r.map(_.sum)
}
}
} yield (r1 ++ r2 ++ r3 ++ r4 ++ r5 ++ r6.flatten).sum
r7 <- DBIO.sequenceOption {
reexport.map { reexportOpt =>
query.map(_.reexport).update(reexportOpt)
}
}
} yield (r1 ++ r2 ++ r3 ++ r4 ++ r5 ++ r6.flatten ++ r7).sum
for {
id <- query.map(_.id).result.headOption
n <- updateQ
@ -893,6 +990,25 @@ final class SqlSuggestionsRepo(val db: SqlDatabase)(implicit
suggestion: Suggestion
): (SuggestionRow, Seq[Suggestion.Argument]) =
suggestion match {
case Suggestion.Module(module, doc, docHtml, reexport) =>
val row = SuggestionRow(
id = None,
externalIdLeast = None,
externalIdMost = None,
kind = SuggestionKind.MODULE,
module = module,
name = module,
selfType = SelfTypeColumn.EMPTY,
returnType = "",
scopeStartLine = ScopeColumn.EMPTY,
scopeStartOffset = ScopeColumn.EMPTY,
scopeEndLine = ScopeColumn.EMPTY,
scopeEndOffset = ScopeColumn.EMPTY,
documentation = doc,
documentationHtml = docHtml,
reexport = reexport
)
row -> Seq()
case Suggestion.Atom(
expr,
module,
@ -900,7 +1016,8 @@ final class SqlSuggestionsRepo(val db: SqlDatabase)(implicit
args,
returnType,
doc,
docHtml
docHtml,
reexport
) =>
val row = SuggestionRow(
id = None,
@ -916,7 +1033,8 @@ final class SqlSuggestionsRepo(val db: SqlDatabase)(implicit
scopeStartLine = ScopeColumn.EMPTY,
scopeStartOffset = ScopeColumn.EMPTY,
scopeEndLine = ScopeColumn.EMPTY,
scopeEndOffset = ScopeColumn.EMPTY
scopeEndOffset = ScopeColumn.EMPTY,
reexport = reexport
)
row -> args
case Suggestion.Method(
@ -927,7 +1045,8 @@ final class SqlSuggestionsRepo(val db: SqlDatabase)(implicit
selfType,
returnType,
doc,
docHtml
docHtml,
reexport
) =>
val row = SuggestionRow(
id = None,
@ -943,7 +1062,8 @@ final class SqlSuggestionsRepo(val db: SqlDatabase)(implicit
scopeStartLine = ScopeColumn.EMPTY,
scopeStartOffset = ScopeColumn.EMPTY,
scopeEndLine = ScopeColumn.EMPTY,
scopeEndOffset = ScopeColumn.EMPTY
scopeEndOffset = ScopeColumn.EMPTY,
reexport = reexport
)
row -> args
case Suggestion.Function(expr, module, name, args, returnType, scope) =>
@ -961,7 +1081,8 @@ final class SqlSuggestionsRepo(val db: SqlDatabase)(implicit
scopeStartLine = scope.start.line,
scopeStartOffset = scope.start.character,
scopeEndLine = scope.end.line,
scopeEndOffset = scope.end.character
scopeEndOffset = scope.end.character,
reexport = None
)
row -> args
case Suggestion.Local(expr, module, name, returnType, scope) =>
@ -979,7 +1100,8 @@ final class SqlSuggestionsRepo(val db: SqlDatabase)(implicit
scopeStartLine = scope.start.line,
scopeStartOffset = scope.start.character,
scopeEndLine = scope.end.line,
scopeEndOffset = scope.end.character
scopeEndOffset = scope.end.character,
reexport = None
)
row -> Seq()
}
@ -1014,6 +1136,13 @@ final class SqlSuggestionsRepo(val db: SqlDatabase)(implicit
arguments: Seq[ArgumentRow]
): Suggestion =
suggestion.kind match {
case SuggestionKind.MODULE =>
Suggestion.Module(
module = suggestion.module,
documentation = suggestion.documentation,
documentationHtml = suggestion.documentationHtml,
reexport = suggestion.reexport
)
case SuggestionKind.ATOM =>
Suggestion.Atom(
externalId =
@ -1023,7 +1152,8 @@ final class SqlSuggestionsRepo(val db: SqlDatabase)(implicit
arguments = arguments.sortBy(_.index).map(toArgument),
returnType = suggestion.returnType,
documentation = suggestion.documentation,
documentationHtml = suggestion.documentationHtml
documentationHtml = suggestion.documentationHtml,
reexport = suggestion.reexport
)
case SuggestionKind.METHOD =>
Suggestion.Method(
@ -1035,7 +1165,8 @@ final class SqlSuggestionsRepo(val db: SqlDatabase)(implicit
selfType = suggestion.selfType,
returnType = suggestion.returnType,
documentation = suggestion.documentation,
documentationHtml = suggestion.documentationHtml
documentationHtml = suggestion.documentationHtml,
reexport = suggestion.reexport
)
case SuggestionKind.FUNCTION =>
Suggestion.Function(
@ -1099,4 +1230,5 @@ final class SqlSuggestionsRepo(val db: SqlDatabase)(implicit
l <- least
m <- most
} yield new UUID(m, l)
}

View File

@ -13,7 +13,7 @@ final class SqlVersionsRepo(db: SqlDatabase)(implicit ec: ExecutionContext)
/** Initialize the repo. */
override def init: Future[Unit] =
db.run(initQuery)
db.run(initQuery.transactionally)
/** @inheritdoc */
override def getVersion(module: String): Future[Option[Array[Byte]]] =
@ -37,7 +37,7 @@ final class SqlVersionsRepo(db: SqlDatabase)(implicit ec: ExecutionContext)
override def updateVersions(
versions: Seq[(String, Array[Byte])]
): Future[Unit] =
db.run(updateVersionsQuery(versions))
db.run(updateVersionsQuery(versions).transactionally)
/** @inheritdoc */
override def remove(module: String): Future[Unit] =

View File

@ -37,11 +37,11 @@ case class ArgumentRow(
* @param name the suggestion name
* @param selfType the self type of a suggestion
* @param returnType the return type of a suggestion
* @param documentation the documentation string
* @param scopeStartLine the line of the start position of the scope
* @param scopeStartOffset the offset of the start position of the scope
* @param scopeEndLine the line of the end position of the scope
* @param scopeEndOffset the offset of the end position of the scope
* @param documentation the documentation string
*/
case class SuggestionRow(
id: Option[Long],
@ -52,12 +52,13 @@ case class SuggestionRow(
name: String,
selfType: String,
returnType: String,
documentation: Option[String],
documentationHtml: Option[String],
scopeStartLine: Int,
scopeStartOffset: Int,
scopeEndLine: Int,
scopeEndOffset: Int
scopeEndOffset: Int,
documentation: Option[String],
documentationHtml: Option[String],
reexport: Option[String]
)
/** A row in the suggestions_version table.
@ -82,10 +83,11 @@ case class ModuleVersionRow(module: String, digest: Array[Byte])
/** The type of a suggestion. */
object SuggestionKind {
val ATOM: Byte = 0
val METHOD: Byte = 1
val FUNCTION: Byte = 2
val LOCAL: Byte = 3
val MODULE: Byte = 0
val ATOM: Byte = 1
val METHOD: Byte = 2
val FUNCTION: Byte = 3
val LOCAL: Byte = 4
/** Create a database suggestion kind.
*
@ -94,6 +96,7 @@ object SuggestionKind {
*/
def apply(kind: Suggestion.Kind): Byte =
kind match {
case Suggestion.Kind.Module => MODULE
case Suggestion.Kind.Atom => ATOM
case Suggestion.Kind.Method => METHOD
case Suggestion.Kind.Function => FUNCTION
@ -152,16 +155,14 @@ final class ArgumentsTable(tag: Tag)
final class SuggestionsTable(tag: Tag)
extends Table[SuggestionRow](tag, "suggestions") {
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def externalIdLeast = column[Option[Long]]("external_id_least")
def externalIdMost = column[Option[Long]]("external_id_most")
def kind = column[Byte]("kind")
def module = column[String]("module")
def name = column[String]("name")
def selfType = column[String]("self_type")
def returnType = column[String]("return_type")
def documentation = column[Option[String]]("documentation")
def documentationHtml = column[Option[String]]("documentation_html")
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def externalIdLeast = column[Option[Long]]("external_id_least")
def externalIdMost = column[Option[Long]]("external_id_most")
def kind = column[Byte]("kind")
def module = column[String]("module")
def name = column[String]("name")
def selfType = column[String]("self_type")
def returnType = column[String]("return_type")
def scopeStartLine =
column[Int]("scope_start_line", O.Default(ScopeColumn.EMPTY))
def scopeStartOffset =
@ -170,6 +171,10 @@ final class SuggestionsTable(tag: Tag)
column[Int]("scope_end_line", O.Default(ScopeColumn.EMPTY))
def scopeEndOffset =
column[Int]("scope_end_offset", O.Default(ScopeColumn.EMPTY))
def documentation = column[Option[String]]("documentation")
def documentationHtml = column[Option[String]]("documentation_html")
def reexport = column[Option[String]]("reexport")
def * =
(
id.?,
@ -180,12 +185,13 @@ final class SuggestionsTable(tag: Tag)
name,
selfType,
returnType,
documentation,
documentationHtml,
scopeStartLine,
scopeStartOffset,
scopeEndLine,
scopeEndOffset
scopeEndOffset,
documentation,
documentationHtml,
reexport
) <>
(SuggestionRow.tupled, SuggestionRow.unapply)
@ -195,6 +201,7 @@ final class SuggestionsTable(tag: Tag)
def returnTypeIdx = index("suggestions_return_type_idx", returnType)
def externalIdIdx =
index("suggestions_external_id_idx", (externalIdLeast, externalIdMost))
def reexportIdx = index("suggestions_reexport_idx", reexport)
// NOTE: unique index should not contain nullable columns because SQLite
// teats NULLs as distinct values.
def uniqueIdx =
@ -205,7 +212,6 @@ final class SuggestionsTable(tag: Tag)
module,
name,
selfType,
returnType,
scopeStartLine,
scopeStartOffset,
scopeEndLine,
@ -258,5 +264,5 @@ object SuggestionsVersion extends TableQuery(new SuggestionsVersionTable(_))
object SchemaVersion extends TableQuery(new SchemaVersionTable(_)) {
/** The current schema version. */
val CurrentVersion: Long = 3
val CurrentVersion: Long = 4
}

View File

@ -3,8 +3,9 @@ package org.enso.searcher.sql
import java.nio.file.{Files, Path}
import java.util.UUID
import org.enso.polyglot.Suggestion
import org.enso.polyglot.{ExportedSymbol, ModuleExports, Suggestion}
import org.enso.polyglot.runtime.Runtime.Api
import org.enso.searcher.data.QueryResult
import org.enso.testkit.RetrySpec
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
@ -63,6 +64,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
for {
_ <- repo.insertAll(
Seq(
suggestion.module,
suggestion.atom,
suggestion.method,
suggestion.function,
@ -74,6 +76,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
val suggestions = Await.result(action, Timeout).map(_.suggestion)
suggestions should contain theSameElementsAs Seq(
suggestion.module,
suggestion.atom,
suggestion.method,
suggestion.function,
@ -85,6 +88,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
val action = for {
(_, ids) <- repo.insertAll(
Seq(
suggestion.module,
suggestion.atom,
suggestion.method,
suggestion.function,
@ -100,7 +104,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
} yield (ids, results)
val (ids, results) = Await.result(action, Timeout)
results should contain theSameElementsInOrderAs Seq(ids(1), None)
results should contain theSameElementsInOrderAs Seq(ids(2), None)
}
"get suggestions by empty method call info" taggedAs Retry in withRepo {
@ -108,6 +112,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
val action = for {
_ <- repo.insertAll(
Seq(
suggestion.module,
suggestion.atom,
suggestion.method,
suggestion.function,
@ -125,6 +130,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
val action = for {
_ <- repo.insertAll(
Seq(
suggestion.module,
suggestion.atom,
suggestion.method,
suggestion.function,
@ -143,6 +149,8 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
for {
(_, ids) <- repo.insertAll(
Seq(
suggestion.module,
suggestion.module,
suggestion.atom,
suggestion.atom,
suggestion.method,
@ -159,7 +167,10 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
val (ids, all) = Await.result(action, Timeout)
ids(0) shouldBe a[Some[_]]
ids(1) shouldBe a[None.type]
ids(2) shouldBe a[Some[_]]
ids(3) shouldBe a[None.type]
all.map(_.suggestion) should contain theSameElementsAs Seq(
suggestion.module,
suggestion.atom,
suggestion.method,
suggestion.function,
@ -208,6 +219,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
val action = for {
(_, idsIns) <- repo.insertAll(
Seq(
suggestion.module,
suggestion.atom,
suggestion.method,
suggestion.function,
@ -226,6 +238,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
val action = for {
(v1, _) <- repo.insertAll(
Seq(
suggestion.module,
suggestion.atom,
suggestion.method,
suggestion.function,
@ -242,8 +255,9 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
"remove all suggestions" taggedAs Retry in withRepo { repo =>
val action = for {
(_, Seq(id1, _, _, id4)) <- repo.insertAll(
(_, Seq(_, id1, _, _, id4)) <- repo.insertAll(
Seq(
suggestion.module,
suggestion.atom,
suggestion.method,
suggestion.function,
@ -385,6 +399,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
"update suggestion by external id" taggedAs Retry in withRepo { repo =>
val newReturnType = "Quux"
val action = for {
_ <- repo.insert(suggestion.module)
_ <- repo.insert(suggestion.atom)
_ <- repo.insert(suggestion.method)
_ <- repo.insert(suggestion.function)
@ -402,8 +417,9 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
"update suggestion external id" taggedAs Retry in withRepo { repo =>
val newUuid = UUID.randomUUID()
val action = for {
(v1, Seq(_, id1, _, _)) <- repo.insertAll(
(v1, Seq(_, _, id1, _, _)) <- repo.insertAll(
Seq(
suggestion.module,
suggestion.atom,
suggestion.method,
suggestion.function,
@ -417,6 +433,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
None,
None,
None,
None,
None
)
s <- repo.select(id1.get)
@ -430,8 +447,9 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
"update suggestion removing external id" taggedAs Retry in withRepo {
repo =>
val action = for {
(v1, Seq(_, _, id1, _)) <- repo.insertAll(
(v1, Seq(_, _, _, id1, _)) <- repo.insertAll(
Seq(
suggestion.module,
suggestion.atom,
suggestion.method,
suggestion.function,
@ -445,6 +463,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
None,
None,
None,
None,
None
)
s <- repo.select(id1.get)
@ -458,8 +477,9 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
"update suggestion return type" taggedAs Retry in withRepo { repo =>
val newReturnType = "NewType"
val action = for {
(v1, Seq(_, _, id1, _)) <- repo.insertAll(
(v1, Seq(_, _, _, id1, _)) <- repo.insertAll(
Seq(
suggestion.module,
suggestion.atom,
suggestion.method,
suggestion.function,
@ -473,6 +493,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
Some(newReturnType),
None,
None,
None,
None
)
s <- repo.select(id1.get)
@ -483,11 +504,12 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
s shouldEqual Some(suggestion.function.copy(returnType = newReturnType))
}
"update suggestion documentation" taggedAs Retry in withRepo { repo =>
"update suggestion atom documentation" taggedAs Retry in withRepo { repo =>
val newDoc = "My Doc"
val action = for {
(v1, Seq(id1, _, _, _)) <- repo.insertAll(
(v1, Seq(_, id1, _, _, _)) <- repo.insertAll(
Seq(
suggestion.module,
suggestion.atom,
suggestion.method,
suggestion.function,
@ -501,6 +523,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
None,
Some(Some(newDoc)),
None,
None,
None
)
s <- repo.select(id1.get)
@ -511,11 +534,109 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
s shouldEqual Some(suggestion.atom.copy(documentation = Some(newDoc)))
}
"update suggestion atom HTML documentation" taggedAs Retry in withRepo {
repo =>
val newDoc = "My Doc"
val action = for {
(v1, Seq(_, id1, _, _, _)) <- repo.insertAll(
Seq(
suggestion.module,
suggestion.atom,
suggestion.method,
suggestion.function,
suggestion.local
)
)
(v2, id2) <- repo.update(
suggestion.atom,
None,
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.atom.copy(documentationHtml = Some(newDoc))
)
}
"update suggestion module documentation" taggedAs Retry in withRepo {
repo =>
val newDoc = "My Doc"
val action = for {
(v1, Seq(id1, _, _, _, _)) <- repo.insertAll(
Seq(
suggestion.module,
suggestion.atom,
suggestion.method,
suggestion.function,
suggestion.local
)
)
(v2, id2) <- repo.update(
suggestion.module,
None,
None,
None,
Some(Some(newDoc)),
None,
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.module.copy(documentation = Some(newDoc)))
}
"update suggestion module HTML documentation" taggedAs Retry in withRepo {
repo =>
val newDoc = "My Doc"
val action = for {
(v1, Seq(id1, _, _, _, _)) <- repo.insertAll(
Seq(
suggestion.module,
suggestion.atom,
suggestion.method,
suggestion.function,
suggestion.local
)
)
(v2, id2) <- repo.update(
suggestion.module,
None,
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.module.copy(documentationHtml = Some(newDoc))
)
}
"update suggestion removing documentation" taggedAs Retry in withRepo {
repo =>
val action = for {
(v1, Seq(id1, _, _, _)) <- repo.insertAll(
(v1, Seq(_, id1, _, _, _)) <- repo.insertAll(
Seq(
suggestion.module,
suggestion.atom,
suggestion.method,
suggestion.function,
@ -529,6 +650,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
None,
Some(None),
None,
None,
None
)
s <- repo.select(id1.get)
@ -539,14 +661,45 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
s shouldEqual Some(suggestion.atom.copy(documentation = None))
}
"update suggestion removing HTML documentation" taggedAs Retry in withRepo {
repo =>
val action = for {
(v1, Seq(_, id1, _, _, _)) <- repo.insertAll(
Seq(
suggestion.module,
suggestion.atom,
suggestion.method,
suggestion.function,
suggestion.local
)
)
(v2, id2) <- repo.update(
suggestion.atom,
None,
None,
None,
None,
Some(None),
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.atom.copy(documentationHtml = None))
}
"update suggestion scope" taggedAs Retry in withRepo { repo =>
val newScope = Suggestion.Scope(
Suggestion.Position(14, 15),
Suggestion.Position(42, 43)
)
val action = for {
(v1, Seq(_, _, _, id1)) <- repo.insertAll(
(v1, Seq(_, _, _, _, id1)) <- repo.insertAll(
Seq(
suggestion.module,
suggestion.atom,
suggestion.method,
suggestion.function,
@ -560,7 +713,8 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
None,
None,
None,
Some(newScope)
Some(newScope),
None
)
s <- repo.select(id1.get)
} yield (v1, id1, v2, id2, s)
@ -575,8 +729,9 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
Api.SuggestionArgumentAction.Remove(1)
)
val action = for {
(v1, Seq(id1, _, _, _)) <- repo.insertAll(
(v1, Seq(_, id1, _, _, _)) <- repo.insertAll(
Seq(
suggestion.module,
suggestion.atom,
suggestion.method,
suggestion.function,
@ -590,6 +745,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
None,
None,
None,
None,
None
)
s <- repo.select(id1.get)
@ -608,8 +764,9 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
Api.SuggestionArgumentAction.Add(3, suggestion.atom.arguments(1))
)
val action = for {
(v1, Seq(id1, _, _, _)) <- repo.insertAll(
(v1, Seq(_, id1, _, _, _)) <- repo.insertAll(
Seq(
suggestion.module,
suggestion.atom,
suggestion.method,
suggestion.function,
@ -623,6 +780,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
None,
None,
None,
None,
None
)
s <- repo.select(id1.get)
@ -649,8 +807,9 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
)
)
val action = for {
(v1, Seq(id1, _, _, _)) <- repo.insertAll(
(v1, Seq(_, id1, _, _, _)) <- repo.insertAll(
Seq(
suggestion.module,
suggestion.atom,
suggestion.method,
suggestion.function,
@ -664,6 +823,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
None,
None,
None,
None,
None
)
s <- repo.select(id1.get)
@ -683,6 +843,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
val action = for {
(v1, _) <- repo.insertAll(
Seq(
suggestion.module,
suggestion.atom,
suggestion.method,
suggestion.function,
@ -696,6 +857,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
None,
None,
None,
None,
None
)
} yield (v1, v2, id2)
@ -707,6 +869,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
"change version after updateAll" taggedAs Retry in withRepo { repo =>
val newReturnType = "Quux"
val action = for {
_ <- repo.insert(suggestion.module)
_ <- repo.insert(suggestion.atom)
_ <- repo.insert(suggestion.method)
_ <- repo.insert(suggestion.function)
@ -725,6 +888,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
repo =>
val newReturnType = "Quux"
val action = for {
_ <- repo.insert(suggestion.module)
_ <- repo.insert(suggestion.atom)
_ <- repo.insert(suggestion.method)
_ <- repo.insert(suggestion.function)
@ -745,6 +909,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
val action = for {
(_, ids) <- repo.insertAll(
Seq(
suggestion.module,
suggestion.atom,
suggestion.method,
suggestion.function,
@ -758,13 +923,14 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
val (ids, xs1, xs2, xs3, xs4, res) = Await.result(action, Timeout)
xs1 should contain theSameElementsAs ids.flatten.map((_, newModuleName))
xs2 should contain theSameElementsAs Seq(ids(1)).flatten
xs2 should contain theSameElementsAs Seq(ids(2)).flatten
.map((_, newSelfType))
xs3 should contain theSameElementsAs Seq(ids(2), ids(3)).flatten
xs3 should contain theSameElementsAs Seq(ids(3), ids(4)).flatten
.map((_, newReturnType))
xs4 should contain theSameElementsAs Seq()
res.map(_.suggestion) should contain theSameElementsAs Seq(
suggestion.atom.copy(module = newModuleName),
suggestion.module.copy(module = newModuleName),
suggestion.atom.copy(module = newModuleName),
suggestion.method
.copy(module = newModuleName, selfType = newSelfType),
suggestion.function
@ -782,7 +948,13 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
val atom = suggestion.atom.copy(module = "Test.Main.Test.Main")
val all =
Seq(atom, suggestion.method, suggestion.function, suggestion.local)
Seq(
suggestion.module,
atom,
suggestion.method,
suggestion.function,
suggestion.local
)
val action = for {
(_, ids) <- repo.insertAll(all)
(_, xs1, xs2, xs3, xs4) <- repo.renameProject("Test", "Best")
@ -797,12 +969,14 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
case (idOpt, _) =>
idOpt.map((_, newModuleName))
}
xs2 should contain theSameElementsAs Seq(ids(1)).flatten
xs2 should contain theSameElementsAs Seq(ids(2)).flatten
.map((_, newSelfType))
xs3 should contain theSameElementsAs Seq(ids(2), ids(3)).flatten
xs3 should contain theSameElementsAs Seq(ids(3), ids(4)).flatten
.map((_, newReturnType))
xs4 should contain theSameElementsAs Seq()
res.map(_.suggestion) should contain theSameElementsAs Seq(
suggestion.module
.copy(module = newModuleName),
atom.copy(module = "Best.Main.Test.Main"),
suggestion.method
.copy(module = newModuleName, selfType = newSelfType),
@ -819,11 +993,12 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
val newFooModuleName = "Best.Foo"
val newReturnTypeName = "Best.Main.MyType"
val module = suggestion.module.copy(module = "Test.Main")
val atom = suggestion.atom.copy(module = "Test.Main")
val method = suggestion.method.copy(module = "Test.Foo")
val function = suggestion.function.copy(module = "Bar.Main")
val local = suggestion.local.copy(module = "Bar.Main")
val all = Seq(atom, method, function, local)
val all = Seq(module, atom, method, function, local)
val action = for {
(_, ids) <- repo.insertAll(all)
(_, xs1, xs2, xs3, xs4) <- repo.renameProject("Test", "Best")
@ -833,19 +1008,22 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
val (ids, xs1, xs2, xs3, xs4, res) = Await.result(action, Timeout)
xs1 should contain theSameElementsAs ids
.zip(Seq(atom, method))
.zip(Seq(module, atom, method))
.flatMap {
case (idOpt, _: Suggestion.Module) =>
idOpt.map((_, newMainModuleName))
case (idOpt, _: Suggestion.Atom) =>
idOpt.map((_, newMainModuleName))
case (idOpt, _) =>
idOpt.map((_, newFooModuleName))
}
xs2 should contain theSameElementsAs Seq(ids(1)).flatten
xs2 should contain theSameElementsAs Seq(ids(2)).flatten
.map((_, newMainModuleName))
xs3 should contain theSameElementsAs Seq(ids(2), ids(3)).flatten
xs3 should contain theSameElementsAs Seq(ids(3), ids(4)).flatten
.map((_, newReturnTypeName))
xs4 should contain theSameElementsAs Seq()
res.map(_.suggestion) should contain theSameElementsAs Seq(
module.copy(module = newMainModuleName),
atom.copy(module = newMainModuleName),
method.copy(module = newFooModuleName, selfType = newMainModuleName),
function.copy(returnType = newReturnTypeName),
@ -873,7 +1051,13 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
)
)
val all =
Seq(suggestion.atom, method, suggestion.function, suggestion.local)
Seq(
suggestion.module,
suggestion.atom,
method,
suggestion.function,
suggestion.local
)
val action = for {
(_, ids) <- repo.insertAll(all)
(_, xs1, xs2, xs3, xs4) <- repo.renameProject("Test", "Best")
@ -883,15 +1067,16 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
val (ids, xs1, xs2, xs3, xs4, res) = Await.result(action, Timeout)
xs1 should contain theSameElementsAs ids.flatten.map((_, newModuleName))
xs2 should contain theSameElementsAs Seq(ids(1)).flatten
xs2 should contain theSameElementsAs Seq(ids(2)).flatten
.map((_, newSelfType))
xs3 should contain theSameElementsAs Seq(ids(2), ids(3)).flatten
xs3 should contain theSameElementsAs Seq(ids(3), ids(4)).flatten
.map((_, newReturnType))
xs4 should contain theSameElementsAs Seq(ids(1)).flatMap {
xs4 should contain theSameElementsAs Seq(ids(2)).flatMap {
_.map((_, 1, newArgumentType))
}
res.map(_.suggestion) should contain theSameElementsAs Seq(
suggestion.atom.copy(module = newModuleName),
suggestion.module.copy(module = newModuleName),
suggestion.atom.copy(module = newModuleName),
method
.copy(
module = newModuleName,
@ -932,8 +1117,162 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
v1 shouldEqual Some(v2)
}
"apply export updates" taggedAs Retry in withRepo { repo =>
val reexport = "Foo.Bar"
val method = suggestion.method.copy(reexport = Some(reexport))
val updates = Seq(
Api.ExportsUpdate(
ModuleExports(
reexport,
Set(ExportedSymbol.Module(suggestion.module.module))
),
Api.ExportsAction.Add()
),
Api.ExportsUpdate(
ModuleExports(
reexport,
Set(ExportedSymbol.Method(method.module, method.name))
),
Api.ExportsAction.Remove()
)
)
val action = for {
(_, ids) <- repo.insertAll(
Seq(
suggestion.module,
suggestion.atom,
method,
suggestion.function,
suggestion.local
)
)
results <- repo.applyExports(updates)
} yield (ids, results)
val (ids, results) = Await.result(action, Timeout)
results should contain theSameElementsAs Seq(
QueryResult(ids(0).toSeq, updates(0)),
QueryResult(ids(2).toSeq, updates(1))
)
}
"not apply exports with bigger module name" taggedAs Retry in withRepo {
repo =>
val reexport = "Foo.Bar.Baz"
val method = suggestion.method.copy(reexport = Some("Foo.Bar"))
val updates = Seq(
Api.ExportsUpdate(
ModuleExports(
reexport,
Set(ExportedSymbol.Module(suggestion.module.module))
),
Api.ExportsAction.Add()
),
Api.ExportsUpdate(
ModuleExports(
reexport,
Set(ExportedSymbol.Method(method.module, method.name))
),
Api.ExportsAction.Remove()
)
)
val action = for {
(_, ids) <- repo.insertAll(
Seq(
suggestion.module,
suggestion.atom,
method,
suggestion.function,
suggestion.local
)
)
results <- repo.applyExports(updates)
} yield (ids, results)
val (ids, results) = Await.result(action, Timeout)
results should contain theSameElementsAs Seq(
QueryResult(ids(0).toSeq, updates(0)),
QueryResult(Seq(), updates(1))
)
}
"change version after applying exports" taggedAs Retry in withRepo { repo =>
val reexport = "Foo.Bar"
val method = suggestion.method.copy(reexport = Some(reexport))
val updates = Seq(
Api.ExportsUpdate(
ModuleExports(
reexport,
Set(ExportedSymbol.Module(suggestion.module.module))
),
Api.ExportsAction.Add()
),
Api.ExportsUpdate(
ModuleExports(
reexport,
Set(ExportedSymbol.Method(method.module, method.name))
),
Api.ExportsAction.Remove()
)
)
val action = for {
_ <- repo.insertAll(
Seq(
suggestion.module,
suggestion.atom,
method,
suggestion.function,
suggestion.local
)
)
v1 <- repo.currentVersion
results <- repo.applyExports(updates)
v2 <- repo.currentVersion
} yield (results, v1, v2)
val (results, v1, v2) = Await.result(action, Timeout)
results.flatMap(_.ids).isEmpty shouldBe false
v1 should not equal v2
}
"not change version when exports not applied" taggedAs Retry in withRepo {
repo =>
val reexport = "Foo.Bar"
val updates = Seq(
Api.ExportsUpdate(
ModuleExports(
reexport,
Set(
ExportedSymbol
.Method(suggestion.method.module, suggestion.method.name)
)
),
Api.ExportsAction.Remove()
)
)
val action = for {
_ <- repo.insertAll(
Seq(
suggestion.module,
suggestion.atom,
suggestion.method,
suggestion.function,
suggestion.local
)
)
v1 <- repo.currentVersion
results <- repo.applyExports(updates)
v2 <- repo.currentVersion
} yield (results, v1, v2)
val (results, v1, v2) = Await.result(action, Timeout)
results.flatMap(_.ids).isEmpty shouldBe true
v1 shouldEqual v2
}
"search suggestion by empty query" taggedAs Retry in withRepo { repo =>
val action = for {
_ <- repo.insert(suggestion.module)
_ <- repo.insert(suggestion.atom)
_ <- repo.insert(suggestion.method)
_ <- repo.insert(suggestion.function)
@ -947,25 +1286,27 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
"search suggestion by module" taggedAs Retry in withRepo { repo =>
val action = for {
id0 <- repo.insert(suggestion.module)
id1 <- repo.insert(suggestion.atom)
id2 <- repo.insert(suggestion.method)
id3 <- repo.insert(suggestion.function)
id4 <- repo.insert(suggestion.local)
res <- repo.search(Some("Test.Main"), Seq(), None, None, None)
} yield (id1, id2, id3, id4, res._2)
} yield (id0, id1, id2, id3, id4, res._2)
val (id1, id2, id3, id4, res) = Await.result(action, Timeout)
res should contain theSameElementsAs Seq(id1, id2, id3, id4).flatten
val (id0, id1, id2, id3, id4, res) = Await.result(action, Timeout)
res should contain theSameElementsAs Seq(id0, id1, id2, id3, id4).flatten
}
"search suggestion by empty module" taggedAs Retry in withRepo { repo =>
val action = for {
id0 <- repo.insert(suggestion.module)
id1 <- repo.insert(suggestion.atom)
id2 <- repo.insert(suggestion.method)
_ <- repo.insert(suggestion.function)
_ <- repo.insert(suggestion.local)
res <- repo.search(Some(""), Seq(), None, None, None)
} yield (res._2, Seq(id1, id2))
} yield (res._2, Seq(id0, id1, id2))
val (res, globals) = Await.result(action, Timeout)
res should contain theSameElementsAs globals.flatten
@ -973,6 +1314,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
"search suggestion by self type" taggedAs Retry in withRepo { repo =>
val action = for {
_ <- repo.insert(suggestion.module)
_ <- repo.insert(suggestion.atom)
id2 <- repo.insert(suggestion.method)
_ <- repo.insert(suggestion.function)
@ -986,6 +1328,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
"search suggestion by return type" taggedAs Retry in withRepo { repo =>
val action = for {
_ <- repo.insert(suggestion.module)
_ <- repo.insert(suggestion.atom)
_ <- repo.insert(suggestion.method)
id3 <- repo.insert(suggestion.function)
@ -1000,6 +1343,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
"search suggestion by kind" taggedAs Retry in withRepo { repo =>
val kinds = Seq(Suggestion.Kind.Atom, Suggestion.Kind.Local)
val action = for {
_ <- repo.insert(suggestion.module)
id1 <- repo.insert(suggestion.atom)
_ <- repo.insert(suggestion.method)
_ <- repo.insert(suggestion.function)
@ -1013,6 +1357,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
"search suggestion by empty kinds" taggedAs Retry in withRepo { repo =>
val action = for {
_ <- repo.insert(suggestion.module)
_ <- repo.insert(suggestion.atom)
_ <- repo.insert(suggestion.method)
_ <- repo.insert(suggestion.function)
@ -1026,6 +1371,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
"search suggestion global by scope" taggedAs Retry in withRepo { repo =>
val action = for {
id0 <- repo.insert(suggestion.module)
id1 <- repo.insert(suggestion.atom)
id2 <- repo.insert(suggestion.method)
_ <- repo.insert(suggestion.function)
@ -1038,29 +1384,31 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
None,
Some(Suggestion.Position(99, 42))
)
} yield (id1, id2, res._2)
} yield (id0, id1, id2, res._2)
val (id1, id2, res) = Await.result(action, Timeout)
res should contain theSameElementsAs Seq(id1, id2).flatten
val (id0, id1, id2, res) = Await.result(action, Timeout)
res should contain theSameElementsAs Seq(id0, id1, id2).flatten
}
"search suggestion local by scope" taggedAs Retry in withRepo { repo =>
val action = for {
id0 <- repo.insert(suggestion.module)
id1 <- repo.insert(suggestion.atom)
id2 <- repo.insert(suggestion.method)
id3 <- repo.insert(suggestion.function)
_ <- repo.insert(suggestion.local)
res <-
repo.search(None, Seq(), None, None, Some(Suggestion.Position(1, 5)))
} yield (id1, id2, id3, res._2)
} yield (id0, id1, id2, id3, res._2)
val (id1, id2, id3, res) = Await.result(action, Timeout)
res should contain theSameElementsAs Seq(id1, id2, id3).flatten
val (id0, id1, id2, id3, res) = Await.result(action, Timeout)
res should contain theSameElementsAs Seq(id0, id1, id2, id3).flatten
}
"search suggestion by module and self type" taggedAs Retry in withRepo {
repo =>
val action = for {
_ <- repo.insert(suggestion.module)
_ <- repo.insert(suggestion.atom)
id2 <- repo.insert(suggestion.method)
_ <- repo.insert(suggestion.function)
@ -1082,6 +1430,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
repo =>
val kinds = Seq(Suggestion.Kind.Atom, Suggestion.Kind.Local)
val action = for {
_ <- repo.insert(suggestion.module)
_ <- repo.insert(suggestion.atom)
_ <- repo.insert(suggestion.method)
_ <- repo.insert(suggestion.function)
@ -1102,6 +1451,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
"search suggestion by return type and scope" taggedAs Retry in withRepo {
repo =>
val action = for {
_ <- repo.insert(suggestion.module)
_ <- repo.insert(suggestion.atom)
_ <- repo.insert(suggestion.method)
_ <- repo.insert(suggestion.function)
@ -1122,6 +1472,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
"search suggestion by kind and scope" taggedAs Retry in withRepo { repo =>
val kinds = Seq(Suggestion.Kind.Atom, Suggestion.Kind.Local)
val action = for {
_ <- repo.insert(suggestion.module)
id1 <- repo.insert(suggestion.atom)
_ <- repo.insert(suggestion.method)
_ <- repo.insert(suggestion.function)
@ -1142,6 +1493,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
"search suggestion by self and return types" taggedAs Retry in withRepo {
repo =>
val action = for {
_ <- repo.insert(suggestion.module)
_ <- repo.insert(suggestion.atom)
_ <- repo.insert(suggestion.method)
_ <- repo.insert(suggestion.function)
@ -1163,6 +1515,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
repo =>
val kinds = Seq(Suggestion.Kind.Atom, Suggestion.Kind.Local)
val action = for {
_ <- repo.insert(suggestion.module)
_ <- repo.insert(suggestion.atom)
_ <- repo.insert(suggestion.method)
_ <- repo.insert(suggestion.function)
@ -1184,6 +1537,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
repo =>
val kinds = Seq(Suggestion.Kind.Atom, Suggestion.Kind.Local)
val action = for {
_ <- repo.insert(suggestion.module)
_ <- repo.insert(suggestion.atom)
_ <- repo.insert(suggestion.method)
_ <- repo.insert(suggestion.function)
@ -1208,6 +1562,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
Suggestion.Kind.Function
)
val action = for {
_ <- repo.insert(suggestion.module)
_ <- repo.insert(suggestion.atom)
_ <- repo.insert(suggestion.method)
_ <- repo.insert(suggestion.function)
@ -1228,6 +1583,14 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
object suggestion {
val module: Suggestion.Module =
Suggestion.Module(
module = "Test.Main",
documentation = Some("This is a main module."),
documentationHtml = Some("<p>This is a main module.</p>"),
reexport = None
)
val atom: Suggestion.Atom =
Suggestion.Atom(
externalId = None,
@ -1239,7 +1602,8 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
),
returnType = "Pair",
documentation = Some("Awesome"),
documentationHtml = Some("")
documentationHtml = Some("<p>Awesome</p>"),
reexport = None
)
val method: Suggestion.Method =
@ -1251,7 +1615,8 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
selfType = "Test.Main",
returnType = "IO",
documentation = None,
documentationHtml = None
documentationHtml = None,
reexport = None
)
val function: Suggestion.Function =

View File

@ -1,3 +1,3 @@
C03EC922F039EB5CC96F93A489453ADDD4FA6EBBF75713B05FE67B48CFF6ACBF
5C2BC7B32FC933355013753E17DC0D4E66F826053D34BD6CDB22401513A17E34
61713B297871FFB55B9BDF492049F90A7756BBEDF91DA31B72F0EFB98221754D
C309ED3582802FAA0EA238BB40A24F34CACC4FC4A36334C260232FF49DCD9C72
0

View File

@ -1,3 +1,3 @@
2D9E29668392299BAE4C1D0D0D7562556712E78CF8FA1022853E42C9E4C05FA1
28E1F445EA0515C2F3E73ABD9DDB73395B5E0F7A84D07038A1117B782E32A1C6
0F4AF9C887470D371F850DF94EF5155B3872A8537D0B753FE5ADD94171DFCC35
0

View File

@ -1,3 +1,3 @@
0AED341E22D16C5722BF722FD5F92039464E9FE3CCD3288D3643F05E09AF62FB
378F1F6DE5E96E55648F0B31C20AF99F1C9190926BE6429B0FC8BE7CAF9E6E47
E4B0E3E0602F9227B8D9334913B2D903C652DADA95732D0F55E26B469E82B89C
0