Skip to content

Commit

Permalink
RD-10005 flag a warning when environment secret not set (#359)
Browse files Browse the repository at this point in the history
- Added support for compilation messages
  • Loading branch information
alexzerntev authored Feb 15, 2024
1 parent c9c5e61 commit b39c7c3
Show file tree
Hide file tree
Showing 41 changed files with 533 additions and 182 deletions.
2 changes: 1 addition & 1 deletion client/src/main/scala/raw/client/api/CompilerService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ final case class FormatCodeResponse(code: Option[String]) extends ClientLspRespo
final case class HoverResponse(completion: Option[Completion]) extends ClientLspResponse
final case class RenameResponse(positions: Array[Pos]) extends ClientLspResponse
final case class GoToDefinitionResponse(position: Option[Pos]) extends ClientLspResponse
final case class ValidateResponse(errors: List[ErrorMessage]) extends ClientLspResponse
final case class ValidateResponse(messages: List[Message]) extends ClientLspResponse
final case class ErrorResponse(errors: List[ErrorMessage]) extends ClientLspResponse
final case class AutoCompleteResponse(completions: Array[Completion]) extends ClientLspResponse

Expand Down
61 changes: 58 additions & 3 deletions client/src/main/scala/raw/client/api/Errors.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,69 @@

package raw.client.api

import com.fasterxml.jackson.annotation.{JsonSubTypes, JsonTypeInfo}
import raw.utils.RawException
import com.fasterxml.jackson.annotation.JsonSubTypes.{Type => JsonType}

/**
* Used for errors that are found during semantic analysis.
* @param message The error message.
* @param positions The positions where the error occurred.
* message The error message.
* positions The positions where the error occurred.
* severity The severity of the error. 1 = Hint, 2 = Info, 4 = Warning, 8 = Error (compliant with monaco editor).
* - The below two should only be set by compiler errors
* code An optional error code.
* tags Indication for the error Unnecessary = 1, Deprecated = 2 (compliant with monaco editor).
*/
final case class ErrorMessage(message: String, positions: List[ErrorRange])

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes(
Array(
new JsonType(value = classOf[HintMessage], name = "hint"),
new JsonType(value = classOf[InfoMessage], name = "info"),
new JsonType(value = classOf[WarningMessage], name = "warning"),
new JsonType(value = classOf[ErrorMessage], name = "hint")
)
)
sealed trait Message {
val message: String
val positions: List[ErrorRange]
val code: String
val tags: List[Int]
val severity: Int
}
final case class HintMessage(
message: String,
positions: List[ErrorRange],
code: String,
tags: List[Int] = List.empty
) extends Message {
val severity: Int = 1
}
final case class InfoMessage(
message: String,
positions: List[ErrorRange],
code: String,
tags: List[Int] = List.empty
) extends Message {
val severity: Int = 2
}
final case class WarningMessage(
message: String,
positions: List[ErrorRange],
code: String,
tags: List[Int] = List.empty
) extends Message {
val severity: Int = 4
}
final case class ErrorMessage(
message: String,
positions: List[ErrorRange],
code: String,
tags: List[Int] = List.empty
) extends Message {
val severity: Int = 8
}

final case class ErrorRange(begin: ErrorPosition, end: ErrorPosition)
final case class ErrorPosition(line: Int, column: Int)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

package raw.client.rql2.api

import raw.client.api.{CompilerService, ErrorMessage, ProgramEnvironment}
import raw.client.api.{CompilerService, Message, ProgramEnvironment}
import raw.compiler.base.source.{BaseNode, Type}
import raw.compiler.common.source.SourceProgram
import raw.utils.AuthenticatedUser
Expand All @@ -34,12 +34,12 @@ trait Rql2CompilerService extends CompilerService {

sealed trait ParseResponse
final case class ParseSuccess(program: SourceProgram) extends ParseResponse
final case class ParseFailure(errorMessages: List[ErrorMessage]) extends ParseResponse
final case class ParseFailure(errorMessages: List[Message]) extends ParseResponse

sealed trait ParseTypeResponse
final case class ParseTypeSuccess(tipe: Type) extends ParseTypeResponse
final case class ParseTypeFailure(errorMessages: List[ErrorMessage]) extends ParseTypeResponse
final case class ParseTypeFailure(errorMessages: List[Message]) extends ParseTypeResponse

sealed trait GetTypeResponse
final case class GetTypeFailure(errors: List[ErrorMessage]) extends GetTypeResponse
final case class GetTypeFailure(errors: List[Message]) extends GetTypeResponse
final case class GetTypeSuccess(tipe: Option[Type]) extends GetTypeResponse
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ import raw.client.api._
import raw.client.rql2.api._
import raw.client.writers.{PolyglotBinaryWriter, PolyglotTextWriter}
import raw.compiler.base
import raw.compiler.base.errors.{BaseError, UnexpectedType, UnknownDecl}
import raw.compiler.base.errors._
import raw.compiler.base.source.BaseNode
import raw.compiler.base.{CompilerContext, TreeDeclDescription, TreeDescription, TreeParamDescription}
import raw.compiler.common.source.{SourceNode, SourceProgram}
import raw.compiler.rql2._
import raw.compiler.rql2.antlr4.{Antlr4SyntaxAnalyzer, ParseProgramResult, ParseTypeResult}
import raw.compiler.rql2.antlr4.{Antlr4SyntaxAnalyzer, ParseProgramResult, ParseTypeResult, ParserErrors}
import raw.compiler.rql2.builtin.{BinaryPackage, CsvPackage, JsonPackage, StringPackage}
import raw.compiler.rql2.errors._
import raw.compiler.rql2.lsp.CompilerLspService
Expand Down Expand Up @@ -143,21 +143,24 @@ class Rql2TruffleCompilerService(maybeClassLoader: Option[ClassLoader] = None)(
val formattedDecls = programDecls.map {
case TreeDeclDescription(None, outType, comment) => rql2TypeToRawType(outType) match {
case Some(rawType) => DeclDescription(None, rawType, comment)
case None =>
return GetProgramDescriptionFailure(List(ErrorMessage("unsupported type", List.empty)))
case None => return GetProgramDescriptionFailure(
List(ErrorMessage(UnsupportedType.message, List.empty, UnsupportedType.code))
)
}
case TreeDeclDescription(Some(params), outType, comment) =>
val formattedParams = params.map {
case TreeParamDescription(idn, tipe, required) => rql2TypeToRawType(tipe) match {
case Some(rawType) => ParamDescription(idn, rawType, None, required)
case None =>
return GetProgramDescriptionFailure(List(ErrorMessage("unsupported type", List.empty)))
case None => return GetProgramDescriptionFailure(
List(ErrorMessage(UnsupportedType.message, List.empty, UnsupportedType.code))
)
}
}
rql2TypeToRawType(outType) match {
case Some(rawType) => DeclDescription(Some(formattedParams), rawType, comment)
case None =>
return GetProgramDescriptionFailure(List(ErrorMessage("unsupported type", List.empty)))
case None => return GetProgramDescriptionFailure(
List(ErrorMessage(UnsupportedType.message, List.empty, UnsupportedType.code))
)
}
}
(idn, formattedDecls)
Expand All @@ -167,14 +170,16 @@ class Rql2TruffleCompilerService(maybeClassLoader: Option[ClassLoader] = None)(
maybeType.map { t =>
rql2TypeToRawType(t) match {
case Some(rawType) => rawType
case None => return GetProgramDescriptionFailure(List(ErrorMessage("unsupported type", List.empty)))
case None => return GetProgramDescriptionFailure(
List(ErrorMessage(UnsupportedType.message, List.empty, UnsupportedType.code))
)
}
},
comment
)
GetProgramDescriptionSuccess(programDescription)
} else {
GetProgramDescriptionFailure(tree.errors)
GetProgramDescriptionFailure(tree.errors.collect { case e: ErrorMessage => e })
}
} catch {
case NonFatal(t) => throw new CompilerServiceException(t, programContext.dumpDebugInfo)
Expand Down Expand Up @@ -224,7 +229,7 @@ class Rql2TruffleCompilerService(maybeClassLoader: Option[ClassLoader] = None)(
val end = ErrorPosition(endValue.getMember("line").asInt, endValue.getMember("column").asInt)
ErrorRange(begin, end)
}
ErrorMessage(message, positions.to)
ErrorMessage(message, positions.to, ParserErrors.ParserErrorCode)
}
EvalValidationFailure(errors.to)
} else {
Expand Down Expand Up @@ -407,7 +412,7 @@ class Rql2TruffleCompilerService(maybeClassLoader: Option[ClassLoader] = None)(
val end = ErrorPosition(endValue.getMember("line").asInt, endValue.getMember("column").asInt)
ErrorRange(begin, end)
}
ErrorMessage(message, positions.to)
ErrorMessage(message, positions.to, ParserErrors.ParserErrorCode)
}
ExecutionValidationFailure(errors.to)
} else {
Expand Down Expand Up @@ -637,18 +642,18 @@ class Rql2TruffleCompilerService(maybeClassLoader: Option[ClassLoader] = None)(

private def parseError(error: String, position: Position): List[ErrorMessage] = {
val range = ErrorRange(ErrorPosition(position.line, position.column), ErrorPosition(position.line, position.column))
List(ErrorMessage(error, List(range)))
List(ErrorMessage(error, List(range), ParserErrors.ParserErrorCode))
}

private def formatErrors(errors: Seq[BaseError], positions: Positions): List[ErrorMessage] = {
private def formatErrors(errors: Seq[CompilerMessage], positions: Positions): List[Message] = {
errors.map { err =>
val ranges = positions.getStart(err.node) match {
case Some(begin) =>
val Some(end) = positions.getFinish(err.node)
List(ErrorRange(ErrorPosition(begin.line, begin.column), ErrorPosition(end.line, end.column)))
case _ => List.empty
}
ErrorMessage(ErrorsPrettyPrinter.format(err), ranges)
CompilationMessageMapper.toMessage(err, ranges, ErrorsPrettyPrinter.format)
}.toList
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright 2023 RAW Labs S.A.
*
* Use of this software is governed by the Business Source License
* included in the file licenses/BSL.txt.
*
* As of the Change Date specified in that file, in accordance with
* the Business Source License, use of this software will be governed
* by the Apache License, Version 2.0, included in the file
* licenses/APL.txt.
*/

package raw.compiler.rql2.tests.lsp

import raw.client.api.{ErrorMessage, WarningMessage}
import raw.compiler.base.errors.{MissingSecretWarning, UnknownDecl}
import raw.compiler.rql2.tests.CompilerTestContext

trait LspCompilationMessagesTest extends CompilerTestContext {

test("should return a waning") { _ =>
val code = """let a = Environment.Secret("a") in a""".stripMargin
val res = validate(code)
res.messages.size should be(1)
res.messages.foreach {
case WarningMessage(message, _, code, _) =>
assert(message == MissingSecretWarning.message)
assert(code == MissingSecretWarning.code)
case _ => fail("Expected a warning message")
}
}

test("should fail to evaluate silently without a warning") { _ =>
val code = """secret(key: string) = Environment.Secret(key)""".stripMargin
val res = validate(code)
res.messages.size should be(0)
}

test("should not output warning if there is a semantic error") { _ =>
val code = """let a = Environment.Secret(asdf) in a""".stripMargin
val res = validate(code)
res.messages.size should be(1)
res.messages.foreach {
case ErrorMessage(message, _, code, _) =>
assert(message == "asdf is not declared")
assert(code == UnknownDecl.code)
case _ => fail("Expected a warning message")
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ import raw.testing.tags.TruffleTests
@TruffleTests class LspRenameTruffleTest extends TruffleCompilerTestContext with LspRenameTest
@TruffleTests class LspValidateTruffleTest extends TruffleCompilerTestContext with LspValidateTest
@TruffleTests class LspWordAutoCompleteTruffleTest extends TruffleCompilerTestContext with LspWordAutoCompleteTest
@TruffleTests class LspCompilationMessagesTruffleTest extends TruffleCompilerTestContext with LspCompilationMessagesTest
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ package raw.compiler.base

import com.typesafe.scalalogging.StrictLogging
import org.bitbucket.inkytonik.kiama.relation.{EnsureTree, LeaveAlone, TreeRelation}
import raw.client.api.ErrorMessage
import raw.client.api.{ErrorMessage, Message}
import raw.compiler.base.errors.{CompilationMessageMapper, ErrorCompilerMessage}
import raw.compiler.base.source._
import raw.compiler.utils.ExtraRewriters

Expand Down Expand Up @@ -50,12 +51,12 @@ abstract class BaseTree[N <: BaseNode: Manifest, P <: N: Manifest, E <: N: Manif

lazy val valid: Boolean = isTreeValid

lazy val errors: List[ErrorMessage] = {
analyzer.errors.map(err => ErrorMessage(format(err), List.empty)).to
lazy val errors: List[Message] = {
analyzer.errors.map(err => CompilationMessageMapper.toMessage(err, List.empty, format)).toList
}

protected def isTreeValid: Boolean = {
val isValid = analyzer.errors.isEmpty
val isValid = analyzer.errors.collect { case e: ErrorCompilerMessage => e }.isEmpty
if (programContext.settings.onTrainingWheels && !isValid) {
// Errors were found.
// We put them as log messages but next calls will try to re-render the tree (if they support that) so we get nice
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,32 +49,42 @@ abstract class SemanticAnalyzer[N <: BaseNode: Manifest, P <: N: Manifest, E <:

import decorators.{chain, Chain}

private def collectErrors(pf: Any ==> Seq[BaseError]): Seq[BaseError] = {
val c = collectNodes[N, Seq, Seq[BaseError]](pf)
private def collectErrors(pf: Any ==> Seq[CompilerMessage]): Seq[CompilerMessage] = {
val c = collectNodes[N, Seq, Seq[CompilerMessage]](pf)
c(tree.root).flatten
}

protected def errorDef: N ==> Seq[BaseError] = PartialFunction.empty[N, Seq[BaseError]]

lazy val errors: Seq[BaseError] = collectErrors {
case n: N if errorDef.isDefinedAt(n) =>
// If an error was triggered by errorDef, this is reported. In addition, however, we also report an additional
// error if our actual/expected types are incompatible. One example why this is important:
// Say we are a FunApp. We trigger an error in errorDef because one of our arguments is wrong.
// But we are also being used in a context where we should not be a function in the first place.
// The check on typeCompatible will also report that error; therefore, we get two errors, which is correct.
// (There may be cases where we end up reporting "an error too much"; if that ever happens, we should consider
// whether the error check should be done with actual/expected types or with specific cases on errorDef.)
val errs = errorDef(n)
n match {
case e: E if errs.isEmpty && !typeCompatible(e) =>
val ExpectedType(expected, hints, suggestions) = expectedType(e)
errs :+ UnexpectedType(e, actualType(e), expected, hints, suggestions)
case _ => errs
protected def errorDef: N ==> Seq[CompilerMessage] = PartialFunction.empty[N, Seq[CompilerMessage]]
protected def nonErrorDef: N ==> Seq[CompilerMessage] = PartialFunction.empty[N, Seq[CompilerMessage]]

lazy val errors: Seq[CompilerMessage] = {
val errors = collectErrors {
// check for errors
case n: N if errorDef.isDefinedAt(n) =>
// If an error was triggered by errorDef, this is reported. In addition, however, we also report an additional
// error if our actual/expected types are incompatible. One example why this is important:
// Say we are a FunApp. We trigger an error in errorDef because one of our arguments is wrong.
// But we are also being used in a context where we should not be a function in the first place.
// The check on typeCompatible will also report that error; therefore, we get two errors, which is correct.
// (There may be cases where we end up reporting "an error too much"; if that ever happens, we should consider
// whether the error check should be done with actual/expected types or with specific cases on errorDef.)
val errs = errorDef(n)
n match {
case e: E if errs.isEmpty && !typeCompatible(e) =>
val ExpectedType(expected, hints, suggestions) = expectedType(e)
errs :+ UnexpectedType(e, actualType(e), expected, hints, suggestions)
case _ => errs
}
case e: E if !typeCompatible(e) =>
val ExpectedType(expected, hints, suggestions) = expectedType(e)
Seq(UnexpectedType(e, actualType(e), expected, hints, suggestions))
}
if (errors.isEmpty) {
collectErrors {
// check for warnings, hints, info
case n: N if nonErrorDef.isDefinedAt(n) => nonErrorDef(n)
}
case e: E if !typeCompatible(e) =>
val ExpectedType(expected, hints, suggestions) = expectedType(e)
Seq(UnexpectedType(e, actualType(e), expected, hints, suggestions))
} else errors
}

final def tipe(e: E): Type = actualType(e)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ package raw.compiler.base

import org.bitbucket.inkytonik.kiama.util.Positions
import raw.client.api._
import raw.compiler.base.errors.CompilationMessageMapper
import raw.compiler.base.source._
import raw.compiler.rql2.antlr4.ParseProgramResult
import raw.utils._
Expand All @@ -34,11 +35,14 @@ abstract class TreeWithPositions[N <: BaseNode: Manifest, P <: N: Manifest, E <:
doParse()
}

override lazy val errors: List[ErrorMessage] = {
override lazy val errors: List[Message] = {
analyzer.errors.map { err =>
getRange(err.node) match {
case Some(range) => ErrorMessage(format(err), List(range))
case _ => ErrorMessage(format(err), List.empty)
{
val range = getRange(err.node) match {
case Some(r) => List(r)
case None => List.empty
}
CompilationMessageMapper.toMessage(err, range, format)
}
}.toList ++ originalResult.errors
}
Expand All @@ -54,7 +58,7 @@ abstract class TreeWithPositions[N <: BaseNode: Manifest, P <: N: Manifest, E <:
}

override protected def isTreeValid: Boolean = {
val isValid = errors.isEmpty
val isValid = errors.collect { case e: ErrorMessage => e }.isEmpty
if (programContext.settings.onTrainingWheels) logTree(isValid)
isValid
}
Expand Down
Loading

0 comments on commit b39c7c3

Please sign in to comment.