Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(language-server): Pass token types to LSP [LNG-285] #999

Merged
merged 32 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
d6c8601
add token info
DieMyst Nov 13, 2023
59408a2
chore(deps): update dependency co.fs2:fs2-io to v3.9.3 (#969)
renovate[bot] Nov 13, 2023
a7e4338
feat(compiler)!: Make `nil` option bottom [LNG-279] (#968)
InversionSpaces Nov 14, 2023
d941f47
feat(compiler): Generate empty calls to `responseHandlerSrv` [LNG-286…
InversionSpaces Nov 17, 2023
7d1f5fb
chore: Fix e2e after renaming flox to fcli (#986)
nahsi Nov 22, 2023
d72d724
feat(api): Use `js.UndefOr` for `defaultServiceId` (#980)
InversionSpaces Nov 22, 2023
c09d7e4
chore(main): release aqua 0.13.0 (#944)
fluencebot Nov 22, 2023
52f0f30
fix(compiler): Allow returning resolved service as ability [LNG-266] …
InversionSpaces Nov 23, 2023
f090412
chore: Fix e2e (#997)
nahsi Nov 27, 2023
e999bc0
chore(deps): update dependency @fluencelabs/interfaces to v0.9.0 (#990)
renovate[bot] Nov 27, 2023
5dd4a39
chore(deps): update dependency com.eed3si9n:sbt-assembly to v2.1.5 (#…
renovate[bot] Nov 27, 2023
9ccb062
fix(e2e): Use `main` branch for `fcli` (#987)
InversionSpaces Nov 27, 2023
f980231
fix(tests): Fix imports in integration tests (#998)
InversionSpaces Nov 27, 2023
38e4e10
fix(deps): update dependency @fluencelabs/js-client to v0.5.3 (#978)
renovate[bot] Nov 27, 2023
c0ac0bf
chore(deps): update dependency @fluencelabs/aqua-lib to v0.8.1 (#976)
renovate[bot] Nov 27, 2023
aade33a
fix TokenInfo
DieMyst Nov 28, 2023
c4d46e0
return TokenInfo with CompilationResult
DieMyst Nov 29, 2023
7980e81
add token info for named types, handle tokens with same names, refact…
DieMyst Dec 1, 2023
9ef9337
Merge branch 'main' into LNG-285-types-info-to-lsp
DieMyst Dec 1, 2023
2c0547e
Merge remote-tracking branch 'origin/main' into LNG-285-types-info-to…
DieMyst Dec 1, 2023
63d79ed
small updates
DieMyst Dec 1, 2023
afb2a22
PR fixes
DieMyst Dec 1, 2023
a99fb7f
ignore integration tests in publishing snapshots
DieMyst Dec 1, 2023
402e30b
fix PR comments
DieMyst Dec 4, 2023
995e4d1
delete duplications, add tests
DieMyst Dec 4, 2023
ce241a8
all to list
DieMyst Dec 6, 2023
e76adc6
token location
DieMyst Dec 6, 2023
33725d1
pr fixes
DieMyst Dec 6, 2023
c541a7e
pr fixes, rewrite tests
DieMyst Dec 7, 2023
00d4645
pr fixes, rewrite teststry to fix test compilation
DieMyst Dec 7, 2023
718cfe8
fix doubled definitions
DieMyst Dec 7, 2023
e545077
don't delete possible duplications
DieMyst Dec 7, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/snapshot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,12 @@ jobs:
registry-url: "https://npm.fluence.dev"
cache: "pnpm"

- run: pnpm -r i
- run: pnpm --filter='!integration-tests' -r i

- name: Set package version
run: node ci.cjs bump-version ${{ steps.version.outputs.id }}

- run: pnpm -r build
- run: pnpm --filter='!integration-tests' -r build

- name: Publish snapshot
id: snapshot
Expand Down
29 changes: 9 additions & 20 deletions aqua-src/antithesis.aqua
Original file line number Diff line number Diff line change
@@ -1,20 +1,9 @@
aqua StreamArgs

export lng280BugWithForEmptyStreamFunc

service StreamService("test-service"):
store(numbers: []u32, n: u32)

func callService(stream: *u32, n: u32):
stream <<- 1
StreamService.store(stream, n)

func returnEmptyStream() -> *u32:
<- *[]

func lng280BugWithForEmptyStreamFunc():
arr = [1,2,3,4,5]
for a <- arr:
str <- returnEmptyStream()
-- passing the function directly won't work, see LNG-290
callService(str, a)
func arr(strs: []string) -> []string:
n = "str"
arr = [n]
<- arr

func ppp() -> []u32:
n = 123
arr = [123]
<- arr
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ lazy val `language-server-api` = crossProject(JSPlatform, JVMPlatform)
lazy val `language-server-apiJS` = `language-server-api`.js
.settings(
scalaJSLinkerConfig ~= (_.withModuleKind(ModuleKind.CommonJSModule)),
scalaJSUseMainModuleInitializer := true
scalaJSUseMainModuleInitializer := false
)
.settings(addBundleJS("../../language-server-npm/aqua-lsp-api.js"))
.enablePlugins(ScalaJSPlugin)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
package aqua.lsp

import aqua.compiler.*
import aqua.compiler.AquaError.{ParserError as AquaParserError, *}
import aqua.compiler.AquaWarning.*
import aqua.compiler.AquaError.SourcesError
import aqua.files.{AquaFileSources, AquaFilesIO, FileModuleId}
import aqua.io.*
import aqua.parser.lexer.{LiteralToken, Token}
import aqua.parser.lift.FileSpan
import aqua.parser.lift.FileSpan.F
import aqua.parser.lift.{FileSpan, Span}
import aqua.parser.{ArrowReturnError, BlockIndentError, LexerError, ParserError}
import aqua.raw.ConstantRaw
import aqua.semantics.*
import aqua.{AquaIO, SpanParser}

import cats.data.Validated
Expand All @@ -23,148 +19,12 @@ import scala.concurrent.ExecutionContext.Implicits.global
import scala.scalajs.js
import scala.scalajs.js.JSConverters.*
import scala.scalajs.js.annotation.*
import scala.scalajs.js.{UndefOr, undefined}
import scribe.Logging

@JSExportAll
case class CompilationResult(
errors: js.Array[ErrorInfo],
warnings: js.Array[WarningInfo] = js.Array(),
locations: js.Array[TokenLink] = js.Array(),
importLocations: js.Array[TokenImport] = js.Array()
)

@JSExportAll
case class TokenLocation(name: String, startLine: Int, startCol: Int, endLine: Int, endCol: Int)

@JSExportAll
case class TokenLink(current: TokenLocation, definition: TokenLocation)

@JSExportAll
case class TokenImport(current: TokenLocation, path: String)

object TokenLocation {

def fromSpan(span: FileSpan): Option[TokenLocation] = {
val start = span.locationMap.value.toLineCol(span.span.startIndex)
val end = span.locationMap.value.toLineCol(span.span.endIndex)

for {
startLC <- start
endLC <- end
} yield {
TokenLocation(span.name, startLC._1, startLC._2, endLC._1, endLC._2)
}

}
}

@JSExportAll
case class ErrorInfo(
start: Int,
end: Int,
message: String,
location: UndefOr[String]
) {
// Used to distinguish from WarningInfo in TS
val infoType: String = "error"
}

object ErrorInfo {

def apply(fileSpan: FileSpan, message: String): ErrorInfo = {
val start = fileSpan.span.startIndex
val end = fileSpan.span.endIndex
ErrorInfo(start, end, message, fileSpan.name)
}

def applyOp(start: Int, end: Int, message: String, location: Option[String]): ErrorInfo = {
ErrorInfo(start, end, message, location.getOrElse(undefined))
}
}

@JSExportAll
case class WarningInfo(
start: Int,
end: Int,
message: String,
location: UndefOr[String]
) {
// Used to distinguish from ErrorInfo in TS
val infoType: String = "warning"
}

object WarningInfo {

def apply(fileSpan: FileSpan, message: String): WarningInfo = {
val start = fileSpan.span.startIndex
val end = fileSpan.span.endIndex
WarningInfo(start, end, message, fileSpan.name)
}
}

@JSExportTopLevel("AquaLSP")
object AquaLSP extends App with Logging {

private def errorToInfo(
error: AquaError[FileModuleId, AquaFileError, FileSpan.F]
): List[ErrorInfo] = error match {
case AquaParserError(err) =>
err match {
case BlockIndentError(indent, message) =>
ErrorInfo(indent._1, message) :: Nil
case ArrowReturnError(point, message) =>
ErrorInfo(point._1, message) :: Nil
case LexerError((span, e)) =>
e.expected.toList
.groupBy(_.offset)
.map { case (offset, exps) =>
val localSpan = Span(offset, offset + 1)
val fSpan = FileSpan(span.name, span.locationMap, localSpan)
val errorMessages = exps.flatMap(exp => ParserError.expectationToString(exp))
val msg = s"${errorMessages.head}" :: errorMessages.tail.map(t => "OR " + t)
(offset, ErrorInfo(fSpan, msg.mkString("\n")))
}
.toList
.sortBy(_._1)
.map(_._2)
.reverse
}
case SourcesError(err) =>
ErrorInfo.applyOp(0, 0, err.showForConsole, None) :: Nil
case ResolveImportsError(_, token, err) =>
ErrorInfo(token.unit._1, err.showForConsole) :: Nil
case ImportError(token) =>
ErrorInfo(token.unit._1, "Cannot resolve import") :: Nil
case CycleError(modules) =>
ErrorInfo.applyOp(
0,
0,
s"Cycle loops detected in imports: ${modules.map(_.file.fileName)}",
None
) :: Nil
case CompileError(err) =>
err match {
case RulesViolated(token, messages) =>
ErrorInfo(token.unit._1, messages.mkString("\n")) :: Nil
case HeaderError(token, message) =>
ErrorInfo(token.unit._1, message) :: Nil
case WrongAST(ast) =>
ErrorInfo.applyOp(0, 0, "Semantic error: wrong AST", None) :: Nil
object AquaLSP extends Logging {

}
case OutputError(_, err) =>
ErrorInfo.applyOp(0, 0, err.showForConsole, None) :: Nil
case AirValidationError(errors) =>
errors.toChain.toList.map(ErrorInfo.applyOp(0, 0, _, None))
}

private def warningToInfo(
warning: AquaWarning[FileSpan.F]
): List[WarningInfo] = warning match {
case CompileWarning(SemanticWarning(token, messages)) =>
WarningInfo(token.unit._1, messages.mkString("\n")) :: Nil
}
import ResultHelper.*

@JSExport
def compile(
Expand Down Expand Up @@ -195,54 +55,14 @@ object AquaLSP extends App with Logging {

logger.debug("Compilation done.")

def locationsToJs(
locations: List[(Token[FileSpan.F], Token[FileSpan.F])]
): js.Array[TokenLink] = {
locations.flatMap { case (from, to) =>
val fromOp = TokenLocation.fromSpan(from.unit._1)
val toOp = TokenLocation.fromSpan(to.unit._1)

val link = for {
from <- fromOp
to <- toOp
} yield TokenLink(from, to)

if (link.isEmpty)
logger.warn(s"Incorrect coordinates for token '${from.unit._1.name}'")

link.toList
}.toJSArray
}

def importsToTokenImport(imports: List[LiteralToken[FileSpan.F]]): js.Array[TokenImport] =
imports.flatMap { lt =>
val (span, str) = lt.valueToken
val unquoted = str.substring(1, str.length - 1)
TokenLocation.fromSpan(span).map(l => TokenImport(l, unquoted))
}.toJSArray

val result = fileRes match {
fileRes match {
case Valid(lsp) =>
val errors = lsp.errors.map(CompileError.apply).flatMap(errorToInfo)
val warnings = lsp.warnings.map(CompileWarning.apply).flatMap(warningToInfo)
errors match
case Nil =>
logger.debug("No errors on compilation.")
case errs =>
logger.debug("Errors: " + errs.mkString("\n"))

CompilationResult(
errors.toJSArray,
warnings.toJSArray,
locationsToJs(lsp.locations),
importsToTokenImport(lsp.importTokens)
)
lspToCompilationResult(lsp)
case Invalid(e) =>
val errors = e.toChain.toList.flatMap(errorToInfo)
logger.debug("Errors: " + errors.mkString("\n"))
CompilationResult(errors.toJSArray)
}
result
}

proc.unsafeToFuture().toJSPromise
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package aqua.lsp

import aqua.parser.lift.FileSpan

import scala.scalajs.js
import scala.scalajs.js.annotation.JSExportAll
import scala.scalajs.js.{UndefOr, undefined}

@JSExportAll
case class CompilationResult(
errors: js.Array[ErrorInfo],
warnings: js.Array[WarningInfo] = js.Array(),
locations: js.Array[TokenLink] = js.Array(),
importLocations: js.Array[TokenImport] = js.Array(),
tokens: js.Array[ExprInfoJs] = js.Array()
)

@JSExportAll
case class ExprInfoJs(location: TokenLocation, `type`: String)

@JSExportAll
case class TokenLocation(name: String, startLine: Int, startCol: Int, endLine: Int, endCol: Int)

@JSExportAll
case class TokenLink(current: TokenLocation, definition: TokenLocation)

@JSExportAll
case class TokenImport(current: TokenLocation, path: String)

object TokenLocation {

def fromSpan(span: FileSpan): Option[TokenLocation] = {
val start = span.locationMap.value.toLineCol(span.span.startIndex)
val end = span.locationMap.value.toLineCol(span.span.endIndex)

for {
startLC <- start
endLC <- end
} yield TokenLocation(span.name, startLC._1, startLC._2, endLC._1, endLC._2)

}
}

@JSExportAll
case class ErrorInfo(
start: Int,
end: Int,
message: String,
location: UndefOr[String]
) {
// Used to distinguish from WarningInfo in TS
val infoType: String = "error"
}

object ErrorInfo {

def apply(fileSpan: FileSpan, message: String): ErrorInfo = {
val start = fileSpan.span.startIndex
val end = fileSpan.span.endIndex
ErrorInfo(start, end, message, fileSpan.name)
}

def applyOp(start: Int, end: Int, message: String, location: Option[String]): ErrorInfo = {
ErrorInfo(start, end, message, location.getOrElse(undefined))
}
}

@JSExportAll
case class WarningInfo(
start: Int,
end: Int,
message: String,
location: UndefOr[String]
) {
// Used to distinguish from ErrorInfo in TS
val infoType: String = "warning"
}

object WarningInfo {

def apply(fileSpan: FileSpan, message: String): WarningInfo = {
val start = fileSpan.span.startIndex
val end = fileSpan.span.endIndex
WarningInfo(start, end, message, fileSpan.name)
}
}
Loading
Loading